Search code examples
c#dependency-injectionunity-containerabstract-factory

Abstract Factory fit into Unity


We are building an application which has a number integration touch points with other systems. We are effectively using Unity for all our dependency injection needs. The whole business layer has been built with an interface driven approach, with the actual implementation being injected at an outer composition root during bootstrap of the application.

We wanted to handle the integration layer in an elegant manner. The business classes and repositories are dependent on IIntegrationController<A, B> interfaces. Several IIntegrationController<A, B> implementations together represent integration with one target system in the background - forming an integration layer. Currently, we hook up everything in the composition root, one shot, at the beginning. The consumers of this interface are also registered with an appropriate InjectionConstrutor and ResolvedParameter up front. Most types are operating with a PerResolveLifetime and the business classes consuming the IIntegrationController are also resolved for each request context separately.

Refer code below.

        // IIntegrationController Family 1
        // Currently the default registration for IIntegrationController types injected into the business classes
        container.RegisterType<IIntegrationController<A, B>, Family1-IntegrationController<A, B>>();
        container.RegisterType<IIntegrationController<C, D>, Family1-IntegrationController<C, D>>();

        // IIntegrationController Family 2 (currently not registered)
        // We want to be able to register this, or manage this set of mapping registrations separately from Family 1,
        // and be able to hook these up dynamically instead of Family-1 on a per-resolve basis
        container.RegisterType<IIntegrationController<A, B>, Family2-IntegrationController<A, B>>();
        container.RegisterType<IIntegrationController<C, D>, Family2-IntegrationController<C, D>>();

        // Repository/Business Class that consume IIntegrationControllers.
        // There is a whole family of IIntegrationController classes being hooked in, 
        // and there are multiple implementations for the family (as shown above). A typical AbstractFactory scenario.
        container.RegisterType(typeof(Repository<Z>), new PerResolveLifetimeManager(),
            new InjectionConstructor(
                new ResolvedParameter<IIntegrationController<A, B>>(), 
                new ResolvedParameter<IIntegrationController<C, D>>())
        );

Problem Statement:

We want the ability to switch an entire family of IIntegrationController<A, B> at runtime. When the business class is being resolved, we want it to be injected with the right version of IIntegrationController<A, B>, based on a request parameter available in the context.

  • A "named" registration based solution would not be scalable, due to two reasons (an entire family of integration classes has to be switched, and it would require clunky name registrations and conditional resolution in the code making it hard to maintain).
  • The solution should work even when there is a chain/hierarchy of resolutions taking place, i.e. the direct consumer of the IIntegrationController is also resolved through Unity, as it is injected into another class dynamically.
  • We have tried a DependencyOverride and ResolveOverride class during resolution, but that would require the whole set of Family-2 IIntegrationController resolutions to be overriden, instead of just being able to switch the whole layer.
  • We understand that instead of injecting a IIntegrationController directly into the business class, an AbstractFactory may have to be injected, but we are not able to get that working, and are not sure where the registration and resolution would happen. If the business class is hooked with an AbstractFactory, first I would have to hook up the right factory per-resolve,
  • Does this require overriding the InjectionFactory? This link suggests an approach, but we were unable to get it to work smoothly.

Solution

  • What's nice about your design is that you already have the right abstractions in place. You use generic abstractions, so the problem can be solved simply by applying the right patterns on top of your already SOLID design.

    In other words, use a proxy:

    // This class should be considered part of your composition root.
    internal class IntegrationControllerDispatcher<TRequest, TResult> 
        : IIntegrationController<TRequest, TResult>
    {
        private readonly IUserContext userContext;
        private readonly Family1_IntegrationController<A, B> family1Controller;
        private readonly Family2_IntegrationController<A, B> family2Controller;
    
        public IntegrationControllerDispatcher(
            IUserContext userContext,
            Family1_IntegrationController<A, B> family1Controller,
            Family2_IntegrationController<A, B> family2Controller) {
            this.userContext = userContext;
            this.family1Controller = family1Controller;
            this.family2Controller = family2Controller;
        }
    
        public TResult Handle(TRequest request) {
            return this.GetController().Handle(request);
        }
    
        private IIntegrationController<TRequest, TResult> GetController() {
            return this.userContext.IsInFamily("family1"))
                ? this.family1Controller
                : this.family2Controller;
        }
    }
    

    With this class you whole configuration can be reduced to about this:

    container.RegisterType<IUserContext, AspNetUserContext>();
    
    container.RegisterType( 
        typeof(IIntegrationController<,>), 
        typeof(IntegrationControllerDispatcher<,>));
    
    container.RegisterType(typeof(Repository<>), typeof(Repository<>));
    

    Note the following:

    • Note the use of the registrations that do an open-generic mapping. You don't have to register ALL your closed versions one by one. You can do it with one line of code.
    • Also note that the types for the different families aren't registered. Unity can resolve them automatically, because our IntegrationControllerDispatcher depends on them directly. This class is a piece of infrastructure logic and should be placed inside your Composition Root.
    • Note that the decision to use a specific family implementation is not made during the time that the object graph is built; It is made at runtime, because the value that determines this is a runtime value. Trying to determine this at the time the object graph is built, will only complicate things, and make it much harder to verify your object graphs.
    • Furthermore, this runtime value is abstracted behind a function call and placed behind an abstraction (IUserContext.IsInFamily in this case, but that's just an example of course).