Refactoring dependencies with Autofac Aggregate Services
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.