Cecil Phillip


Champions aren’t made in the gyms. Champions are made from something they have deep inside them – a desire, a dream, a vision. – Muhammad Ali

Tags


Cecil Phillip

Refactoring dependencies with Autofac Aggregate Services

31st May 2017

If you make considerable usage of constructor injection and the dependency inversion principle in your applications, you may eventually run into a situation where you have a fairly long list of dependencies that need to get provided to your class.

More often than not, this may be the cause of your class trying to be responsible for too much functionality. At this point, you'd probably take the route of breaking out some of that functionality into another class or service. Another reason for a growing dependency list might be due to your class' inheritance hierarchy. The parent class might require its own set of dependencies to be injected, and this would require the sub-classes to pass on these dependencies to their parent. What would happen if updates to the parent class add additional dependencies? All of sub-classes now would need to be updated to provide these also.

In this short post we will explore the option of using aggregate services to clean up our code and also shield our sub-classes from changes in the parent class dependencies.

What's an Aggregate Service?

What an aggregate service allows you to do is treat a collection of dependencies as a single dependency. As you can image, if a class depends on having multiple classes or services injected into it, we can simplify its API by moving these dependencies into a single one.

Imagine we have some code that looks something like this.

public class OrderRequestHandler : BaseRequestHandler
{
    private IPaymentProcessor _paymentProcessor;

    public OrderRequestHandler(IPaymentProcessor paymentProcessor,
                               ILogger logger,
                               IValidator validator) : base(logger, validator)
    {
        _paymentProcessor = paymentProcessor;
    }
}

public class BaseRequestHandler
{
    private readonly ILogger _logger;
    private IValidator _validator;
    public BaseRequestHandler(ILogger logger, IValidator validator)
    {
        _logger = logger;
        _validator = validator;
    }
}

The OrderRequestHandler class takes in three dependencies. Two of those dependencies are needed by the parent class. Adding another dependency to the parent, like IDataStore for instance, will require all of its sub-classes to be updated.

public class OrderRequestHandler : BaseRequestHandler
{
    private IPaymentProcessor _paymentProcessor;

    public OrderRequestHandler(IPaymentProcessor paymentProcessor, 
                               ILogger logger,
                               IValidator validator,
                               IDataStore dataStore) 
                               : base(logger, validator, dataStore)
    {
        _paymentProcessor = paymentProcessor;
    }
}

public class BaseRequestHandler
{
    private readonly ILogger _logger;
    private IValidator _validator;
    private IDataStore _dataStore;

    public BaseRequestHandler(ILogger logger,
                              IValidator validator,
                              IDataStore dataStore)
    {
        _logger = logger;
        _validator = validator;
        _dataStore = dataStore;
    }
}

Using aggregate services, we can introduce another abstraction to contain some of these "core services" that will get passed on to the parent class. In the code below, we are using a simple interface to model the aggregate service.

 public interface ICoreServices {
      IValidator Validator {get;}
      IDataStore DataStore {get;}
 }
Implementing Aggregate Services with Autofac

Implementing an aggregate service by hand is relatively trivial to accomplish. This would require building a class that accepts the dependencies as constructor arguments and then exposing those dependencies as properties.

Even though this is not hard to do, Autofac provides the handy Autofac.Extras.AggregateService NuGet package that will wire all of this up for you. After installing the package you will have a RegisterAggregateService method available off of ContainerBuilder, which is what we'll use to set things up. Using the code samples above, the Autofac registration code would look like this.

builder.RegisterAggregateService<ICoreServices>();
builder.RegisterType<MyValidator>().As<IValidator>();
builder.RegisterType<MyDataStore>().As<IDataStore>();
builder.RegisterType<MyPaymentProcessor>().As<IPaymentProcessor>();

Now we can update the OrderRequestHandler and BaseRequestHandler classes.

 public class OrderRequestHandler : BaseRequestHandler
 {
     private IPaymentProcessor _paymentProcessor;

     public OrderRequestHandler(IPaymentProcessor paymentProcessor, 
                                ICoreServices coreServices)
                                : base(coreServices)
     {
         _paymentProcessor = paymentProcessor;
     }
 }

 public class BaseRequestHandler
 {
     private ICoreServices _coreServices;

     public BaseRequestHandler(ICoreServices coreServices)
     {
         _coreServices = coreServices;
     }
 }

What's happening behind the scenes is that the Autofac.Extras.AggregateService is generating a proxy for the ICoreServices interface using DynamicProxy. On running the application, the generated proxy will get injected into OrderRequestHandler. When a property is requested, it will be resolved from the Autofac container.

Conclusion

Implementing Aggregate Services is far from a silver bullet solution to cleaning up a large number of dependencies, but I think it's an interesting option to explore. Take a look at the following links for some further discussion on the topic.

If you want to learn more about Autofac and its Aggregate Services extension, head over to their website and check out the documentation site.

View Comments