C# – Dependency injection for a library with internal dependencies

cdependency-injectioninversion-of-controlnet

Background

I am working on a class library in support of a web site. The library combines related APIs from several different vendors, all of which have their own particular nuances and domain objects. As an example, imagine there is a credit card provider that exposes a BankAccount object, a loans provider that exposes an Account, and a checking account system exposes an AccountDto. The primary design goal of the library is to hide these various implementations and allow the web site to work with a single concept of Account, with a set of services that decide which vendor to call and how to coordinate the data from the various sources.

To hide the confusion and keep the API clean, only my own domain objects and services are public. Inside the library, each vendor has its own repository and domain objects. The vendor's classes are all marked internal to keep the web developers from circumventing the services layer (either intentionally or unintentionally).

So the flow goes like this:

Web Site >> My API >> Service Layer (public) >> Repository Layer (internal)

This solution uses dependency injection and the Unity IoC container. My APIs' services are registered in the composition root and injected directly into the web sites' controllers as constructor arguments. All of this works fine.

The problem

Here is the problem I am struggling with. My services have constructor arguments that allow the repositories to be injected into them. But the repositories are internal so the web site cannot register them in the composition root (without doing something truly bizarre with Reflection). Plus I don't want the web site developers to worry about injecting them.

So the question is: How do I inject the internal/private repositories into the public service layer, while adhering to the accepted conventions in a DI approach?

My proposed solution

The way I am doing this right now is as follows:

I have added an abstract RepositoryLocator interface and class to the library. It has its own Unity container. It is left unsealed so that a unit testing project can derive its own version and substitute stub repositories.

public interface IRepositoryLocator
{
    T GetRepository<T>() where T : IRepository;
}

public abstract class BaseRepositoryLocator : IRepositoryLocator
{
    protected readonly UnityContainer _container = new UnityContainer();

    public virtual T GetRepository<T>() where T : IRepository
    {
        return _container.Resolve<T>();
    }
}

Then I added a DefaultRepositoryLocator to my library that is hardcoded to provide the production runtime repositories.

public sealed class DefaultRepositoryLocator : BaseRepositoryLocator
{
    public DefaultRepositoryLocator()
    {
        RegisterDefaultTypes();
    }
    private void RegisterDefaultTypes()
    {
        _container.RegisterType<IVendorARepository, VendorARepository>();
        _container.RegisterType<IVendorBRepository, VendorBRepository>();
        _container.RegisterType<IVendorCRepository, VendorCRepository>();
    }
}

Then in my services classes, instead of injecting repositories, I inject the repository locator, and pull out the repositories that are needed in the constructor.

public class AccountService : IAccountService
{
    internal readonly IVendorARepository _vendorA;
    internal readonly IVendorBRepository _vendorB;

    public AccountService(IRepositoryLocator repositoryLocator)
    {
        _vendorA = repositoryLocator.GetRepository<IVendorARepository>();
        _vendorB = repositoryLocator.GetRepository<IVendorBRepository>();
    }
}

The web development team's only task

In the web site's composition root, they just need to register the default repository locator in their Unity container.

container.RegisterType<IRepositoryLocator, DefaultRepositoryLocator>();

Once they do that, the Unity container injects the repository locator into all the services that are injected into controllers, and the services can retrieve the repositories they need.

Unit testing

In a unit testing project, the unit testing assembly will be given access to the internal members using the InternalsVisibleTo attribute, and the test project would substitute its own StubRepositoryLocator conformed to the internal-but-now-public interfaces.

Things that are troublesome about this design

So, all you IoC experts, what sort of problems have I created? Here's what I worry about

  1. There are two Unity containers, one in the web application and one in the library's RepositoryLocator. I hear there really ought to be only one, but I also read that it is an anti-pattern to pass it around.
  2. The RepositoryLocator is hardcoded for production runtime. I don't think harcoding is a cardinal sin like many engineers do, but I do regard it with suspicion.
  3. The unit testing project will need InternalsVisibleToAttribute added to the library. This is definitely a code smell and I wonder how we would deal with it if the unit testing project changes its name or if there needs to be several of them.

  4. The constructors for my services take one injection for the RepositoryLocator itself, instead of separate injections for the individual repositories. This conceals the individual dependencies at compile time.

Is there an approach that resolves these issues and is consistent with IoC best practices, while still concealing the repository layer behind internal scope?

Best Answer

In the web site's composition root, they just need to register the default repository locator

IMO the web site doesn't even need to know about your repositories. As you said, the repos and vendors classes are internal, so your site should not worry about accessing them or injecting them into somewhere. Again, as you said, it is your library's role to abstract multiple vendors and expose a simpler API to the user (which is the web site in your example).

Having said that, I think you should:

  • In your API, provide some initial entry point method, that needs to be executed once by the client in order that your library works appropriately;
  • Within this method (that's within your library), you configure the container with vendors repositories classes (internal to your library);
  • Keep vendors stuff private and your services public
  • That's it: your client (the website) need to worry only about injecting your services, nothing more than that.
  • Optional: you could use configuration file to bind interfaces and implementations

About your question / worries:

  1. You (usually) have no control about what your client will do; the client app might not even inject your classes, it could just use them right away; so don't worry about that, like said in the comments;
  2. Config file might be a better approach for this, easily changeble for production or test environments;
  3. Regarding unit testing purposes, I see no problem with this attribute;
  4. My suggestion solves this