IoC/DI – Design for Class Library

designinversion-of-control

I am refactoring and introducing unit tests in a large application.

It's currently a collection of static classes with static methods that return data, like such:

// in data access project
public static class DataStock {
     public static Stock GetStock(int operation) { return my data; }
}

// in business layer project
public static class Stock {
     public static Stock GetStock(int operation) {
         var stock = DataStock.GetStock(operation);
         // do something with the data
         return stock;
     }
}

// in forms project
public class FrmDisplayStock {
     public void LoadStock() {
         var stock = Stock.GetStock(this.selectorOperation);
         // display the stock
     }
}

I am redesigning the app like this:

// in data access project
public class DataStock : IDataStock {
     public Stock GetStock(int operation) { return my data; }
}

// in business layer project
public class Stock : IStock {
     private IDataStock data;

     public Stock() { this.data = new DataStock(); }
     public Stock(IDataStock data) { this.data = data; }

     public Stock GetStock(int operation) {
         var stock = data.GetStock(operation);
         // do something with the data
         return stock;
     }
}

// in forms project
public class FrmDisplayStock {
     private IStock stockbll;

     public FrmDisplayStock() { this.stockbll = new Stock(); }
     public FrmDisplayStock(IStock stockbll) { this.stockbll = stockbll; }

     public void LoadStock() {
         var stock = stockbll.GetStock(this.selectorOperation);
         // display the stock
     }
}

This allows me to write tests for the business and forms layers, mocking the underlying layer (I am not yet testing the data access layer, but it's both more complicated to test and less interesting).

I would like to use a IoC container so that I don't have to manually wire the interfaces to concrete types every time; with deep nesting and many tests to write, it's becoming quite annoying and error-prone.

The problem is that I don't grasp how to implement it, technically.

  • Shall I write a bootstrapper for each project that wires the underlying layer ?
  • Shall I change my constructors to get the container's instance like such ? public Stock() { this.data = myContainer.GetInstance<IDataStock>(); }
  • Is it working differently than I am assuming ?

Best Answer

You want all dependencies to be injected from the absolute top layer, so you should have a dependency graph with a root, and the root has a reference to the concrete implementation of the deps at every node, registers that concretion for the interface in a container, and then grabs just the first level deps it needs from the container. But in doing so, that layer has deps the container constructs which has deps the container constructs...

Think of your dependency graph being constructed by the container like this:

public void main()
{
    var frmDisplayStock = new FrmDisplayStock(
      new Stock(
        new DataStock()
      )
    );
}

Except instead of you manually constructing each dependency at each layer of the graph, you just register the types with a container (or however the container you choose requires you to register them). For example:

public void main()
{
    var container = new SomeIocContainer();
    container.Register<IDataStock>(typeof(DataStock));
    container.Register<IStock>(typeof(Stock));
    container.Register<IDataStock>(typeof(DataStock));
    var frmDisplayStock = container.Get<FrmDisplayStock>();
}

In the above example, the final .Get call is going to use the types it knows about, and the container will presumably figure out what types are required to fulfill the whole dependency graph, identically so what was done in our previous example without a container.


So in conclusion, you want to go to the highest point in your stack you can and let it decide all the concretions. You don't want to have those default public constructors that know the concretions, because that causes tightly-coupled references between things when an interface would more than suffice. You never know when you'll want to use an IDataStock that works off a high performance cache, or 3rd party webservice, or local file, so don't presume DataStock is the only implementation you'll use. The key point of making this decision at the top layer is, if you do it below that, you'll find some layer of dependencies above that point and now you'll what- reference up the stack to define it?

Dependencies should be a directed graph with no loops (DAG) and a single root (tree), in a tree you never let peers know about each other, and you never let children know their parents. So where but at the root can you reference all dependencies without breaking the rules of a tree?


Another larger example that may make this all clearer since your example merely has 2 dependencies, not much of a sample for illustrating a concept.

public void Main()
{
    var myWidgetsService = new WidgetService(
      new WidgetEventsProcessor(
        new MessageQueueSubscriber(ConfigurationManager.AppSettings["MQConnectionString"])
      ),
      new WidgetInProcessCacheStore(),
      new WidgetSerializerService(
        new JsonSerializer()
      ),
      new WidgetAccessControlService(
        new AccessRightsProcessor(
          new AccessRightsServiceClient(ConfigurationManager.AppSettings["AccessRightsServiceUri"])
        ),
        new AuthenticationProcessor(
          new WindowsAuthenticationChecker(ConfigurationManager.AppSettings["DomainController"]),
          new EMailClient(ConfigurationManager.AppSettings["SMTPService"]) // for auth failure warnings
        )
      )
    );
}

Given the above dependencies tree, you would just make the registrations, and let the container manage the construction (totally made-up IoC API, not a real or suggested IoC framework API):

public void Main()
{
    var container = new SomeIocContainer();
    container.Register<IWindowsAuthenticationChecker>(typeof(WindowsAuthenticationChecker), ConfigurationManager.AppSettings["DomainController"]);
    container.Register<IEMailClient>(typeof(EMailClient), ConfigurationManager.AppSettings["SMTPService"]);
    container.Register<IAuthenticationProcessor>(typeof(AuthenticationProcessor));
    container.Register<IAccessRightsServiceClient>(typeof(AccessRightsServiceClient), ConfigurationManager.AppSettings["AccessRightsServiceUri"]);
    container.Register<IAccessRightsProcessor>(typeof(AccessRightsProcessor));
    container.Register<IWidgetAccessControlService>(typeof(WidgetAccessControlService));
    container.Register<IJsonSerializer>(typeof(JsonSerializer));
    container.Register<IWidgetSerializerService>(typeof(WidgetSerializerService));
    container.Register<IWidgetStore>(typeof(WidgetInProcessCacheStore));
    container.Register<IWidgetSerializerService>(typeof(WidgetSerializerService));
    container.Register<IMessageQueueSubscriber>(typeof(MessageQueueSubscriber), ConfigurationManager.AppSettings["MQConnectionString"]));
    container.Register<IWidgetEventsProcessor>(typeof(WidgetEventsProcessor));

    var myWidgetsService = container.Get<WidgetService>();
}