|
AggregateService
Dynamically generate aggregate services.
Autofac2 IntroductionAn aggregate service is useful when you need to treat a set of dependencies as one dependency. When a class depends on several constructor-injected services, or have several property-injected services, moving those services into a separate class yields a simpler API. An example is super- and subclasses where the superclass have one or more constructor-injected dependencies. The subclasses must usually inherit these dependencies, even though they might only be useful to the superclass. With an aggregate service, the superclass constructor parameters can be collapsed into one parameter, reducing the repetitiveness in subclasses. Another important side effect is that subclasses are now insulated against changes in the superclass dependencies, introducing a new dependency in the superclass means only changing the aggregate service definition. This example is further elaborated here. Aggregate services can be implemented by hand, e.g. by building a class with constructor-injected dependencies and exposing those as properties. Writing and maintaining aggregate service classes and accompanying tests can quickly get tedious though. The AggregateService extension to Autofac lets you generate aggregate services directly from interface definitions without having to write any implementation. Under the cover, the AggregateService uses DynamicProxy2 from the Castle Project. Given an interface (the aggregate of services into one), a proxy is generated implementing the interface. The proxy will translate calls to properties and methods into Resolve calls to an Autofac context. Required References
Getting StartedLets say we have a class with a number of constructor-injected dependencies that we store privately for later use: public class SomeController
{
private readonly IMyService _myService
private readonly ISomeOtherService _someOtherService;
...
public SomeController(IMyService myService, ISomeOtherService someOtherService, ...)
{
_myService = myService;
_someOtherService = someOtherService;
...
}
}To aggregate the dependencies we move those into a separate interface definition and takes a dependency to that interface instead. public interface IMyAggregateService
{
IMyService MyService { get; }
ISomeOtherService SomeOtherService { get; }
...
}
public class SomeController
{
private readonly IMyAggregateService _services;
public SomeController(IMyAggregateService services)
{
_services = services;
}
}Finally, we register the aggregate service interface. using AutofacContrib.AggregateService;
var builder = new ContainerBuilder();
builder.RegisterAggregateService<IMyAggregateService>();
builder.Register(...).As<IMyService>();
builder.Register(...).As<IMyOtherService>();
builder.Register<SomeController>();
var container = builder.Build();How services are resolvedPropertiesRead-only properties mirror the behavior of regular constructor-injected dependencies. The type of each property will be resolved and cached in the aggregate service when the aggregate service instance is constructed. Here is a functionally equivalent sample: class MyAggregateServiceImpl: IMyAggregateService
{
private IMyService _myService;
public MyAggregateServiceImpl(IComponentContext context)
{
_myService = context.Resolve<IMyService>();
}
public IMyService MyService
{
get { return _myService; }
}
}MethodsMethods will behave like factory delegates and will translate into a resolve call on each invocation. The method return type will be resolved, passing on any parameters to the resolve call. A functionally equivalent sample of the method call: class MyAggregateServiceImpl: IMyAggregateService
{
...
public ISomeThirdService GetThirdService(string data)
{
var dataParam = new TypedParameter(typeof(string), data);
return _context.Resolve<ISomeThirdService>(dataParam);
}
}Property setters and void methodsProperty setters and methods without return types does not make sense in the aggregate service. Their presence in the aggregate service interface does not prevent proxy generation. Calling such methods though will throw an exception. Performance considerationsDue to the fact that method calls in the aggregate service pass through a dynamic proxy there is some overhead on each method call. A performance study on Castle DynamicProxy2 vs other frameworks can be found here. | |