This article summarizes some thoughts about the potentials
of PostSharp with Dependency Injection frameworks. The aim is to support some
ongoing or future discussions about loose integration of PostSharp into major DI
frameworks. It does not describe something that already exist "off-the-shelf" as
a downloadable plug-in.
Container or Service Locator
Most dependency injection (DI) frameworks today require instances to be
constructed using a "factory", "object builder" or "container".
This approach also uses a lot of System.Reflection to set up properties. And
eventually "bad" reflection: the one that breaks the object encapsulation and
accesses private members. You may care or not. I do.
Another major drawback of the "factory" approach is that you cannot use the
standard constructors.
This may be just annoying, but can become really blocking if your constructor
is called from a part of the program that you don't have control on. For
instance, what is you want to inject dependencies into a WinForms user
object? When the user object will be added into a form, Visual Studio will
instantiate it using the default constructor.
Anyway, why does the consumer of an object need to know that a special
factory method should be used? Is our abstraction so shiny?
Of course, there is a solution: inside the constructor, initialize each
dependency by a call to a service locator. It is not
literally an "injection" of dependencies: dependencies are created from
outside.
Container or Context
How does the service locator know how to create the dependency? Where does it
take the configuration from, since the object got no reference to its
container? Well, actually, the object has no container! The service locator
has simply context-sensitive configuration. Each thread has its own context
from which the service locator know how to create the dependency. Even better
than contexts, stacks of contexts allow to have nested contexts just
like we have nested transactions.
An elegant solution is to distinguish between global contexts
(static ones) and local contexts (thread-specific). When a
dependency is resolved, the service locator always looks for a match from the
top of the context stack to the bottom. The stack of local contexts is always
on the top of the stack of global contexts.
The use of local contexts makes it possible to execute unit tests in multiple
threads while using different mocking implementations of dependencies.
For instance, the configuration of a local context may look like this:
using ( new LocalContext("Test") )
{
Context.CurrentContext.Bind<ICustomerProcesses>().To<CustomerProcesses>();
Context.CurrentContext.Bind<IRentalProcesses>().To<RentalProcesses>();
TestRental test = new TestRental();
test.Go();
}
Et PostSharp dans tout ça?
The drawback of the Service Locator pattern is that you need to write
boiler-plate code. For each field, you have to call the service locator.
This is true for those who don't know PostSharp. But friends of
aspect-oriented programming surely noticed an opportunity to let the machine
generate code for you!
Wouldn't it be nice to be able to declare a dependency just like this:
class CustomerProcesses : ICustomerProcesses
{
[Inject]
private IRentalProcesses rentalProcesses;
}
Wouldn't it be nice to have a custom attribute that adds in all constructors
calls to the service locator?
Well, for those who are comfortable with PostSharp Core, it is a matter of
hours to develop it. Users of PostSharp Laos will unfortunately have to wait
until they get an off-the-shelf implementation. But, promised, it will come
soon!
What it means for Dependency Injection frameworks?
What does it mean for DI frameworks to be compatible with this approach?
Apart from these relatively small side requirements (only container nesting
is a little extravagant, but it's a nice-to-have both for the
container- and the context- based approach), the use of an aspect weaver does
not interfere too much with the design of the DI framework. So it is
perfectly possible to have to have a DI framework that is AOP-enabled, but
still excellent without AOP.