Pregunta

I have a scenario where I wrote a series of classes that are used for reading Incidents (i.e. Support Tickets) from different sources. Source 1 is a legacy system, whereas Source2 is the current system, and Source 3 has data for all Incidents.

In an attempt to make it more modular, I created a Service class that is used as the agnostic interface for my web site so that I can just call a single method while passing in any Incident Number and then have the service determine which source to get the data from.

There is also a "loader" Factory that figures out which Source to use and returns an object that is used to get the data from a specific source.

Here is some sample code:

public class IncidentService() : IIncidentService {

   public Incident GetIncident(string incidentId) {
      // ** the bulk of the data comes from one of several (two in this case) sources
      IIncidentLoader loader = IncidentLoaderFactory.GetIncidentLoader(incidentId);

      var incident = loader.GetIncident(incidentId);

      // ** however, there is some data in a 3rd source that applies to all incidents
      var votingSvc = new IncidentVotingService();
      incident.NumberVotes = votingSvc.GetNumberVotes(incidentId);

      return incident;
   }

}

public class IncidentLoaderFactory {

   public static IIncidentLoader GetIncidentLoader(string incidentId) {
      // use regex to check the format of the incident id to determine which source has the data
      if (**format 1**) {
         return new Source1Loader();
      else {
         return new Source2Loader();
      }
   }

}

The way this gets used is very simple...

IIncidentService svc = new IncidentService();
var incident = svc.GetIncident(incidentId);

While this design works in production very well, it does open up some questions and issues

  1. While the code does not show it, I do need to pass in Connection Strings to the different services and loaders. For example, I have the following constructor

    public IncidentService(string source1ConnectionString, string source2ConnectionString, string source3ConnectionString) {  // do something with the parameters }
    

    Obviously this is not flexible -- (1) what if I want to unit test with mock loaders and (2) what is I need to add more loaders

  2. The DI pattern says to pass in instance objects for interface parameters but in this case, the service is using a factory class to get the loader. The factory class in turn then uses regex to determine which loader to return. As before, this does not lend itself to any sort of isolated unit testing. (I can only test the Incident Service against real sources.)

Any ideas on how to tweak this to be more Unit Testing and DI friendly? One thought was to remove the loader factory and instead allow the registration of loaders with the service itself. Then I could either query all loaders for a given incident (with those loaders not hosting the incident returning null) or add a method to the loader interface that returns a true/false indicating if it is the correct loader for the given incident id regex format.

¿Fue útil?

Solución

Dependency Injection means your service should not create the IncidentVotingService, neither have a hardcoded reference to IncidentLoaderFactory. They should be injected, to allow you to replace them with mock object during tests.

The IncidentService could look like this:

public class IncidentService {

    private IIncidentLoaderFactory factory;
    private IIncidentVotingService votingService;

    public IncidentService (IIncidentLoaderFactory factory, IIncidentVotingService votingService){
        this.factory = factory;
        this.votingService = service;
    }

   public Incident GetIncident(string incidentId) {
      // ** the bulk of the data comes from one of several (two in this case) sources
      IIncidentLoader loader = factory.GetIncidentLoader(incidentId);

      var incident = loader.GetIncident(incidentId);

      // ** however, there is some data in a 3rd source that applies to all incidents
      incident.NumberVotes = votingService.GetNumberVotes(incidentId);

      return incident;
   }

}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top