Note: I have posted a follow-up post with some corrections to this post, thanks to some comments made on Reddit: A brief comparison of AOP techniques
Let me start with saying that in this post I will explain why I think Dependency Injection is a better way to solve cross cutting concerns than AOP. I know there are heavy debates for each side, but I will try to explain my motives. I will only be talking about DI in the context of Interception here.
Both AOP and DI let you solve cross cutting concerns by enabling you to write aspects separately from the rest of your code base and then decorate them around the rest.
In terms of how this is achieved both solutions differ greatly.
Aspect Oriented frameworks do their magic by letting you apply attributes to methods and/or properties. Upon compilation, these attributes are read and a post-compilation step is executed. This effectively rewrites the generated MSIL-code, injecting the aspect.
Interception through a DI-container works by configuring aspects on interfaces and methods. When an object is requested, the DI container will dynamically build code and wrap the requested object with the dynamically created class which implements the same interface. This dynamically created class will execute the aspect before or after the requested method is executed. This is similar to the decorator-pattern with the exception of the dynamic construction of the wrapper.
Advantages of DI interception over AOP frameworks
In my opinion the DI-approach is more desirable for the following reasons:
- The goal of AOP is to achieve loose coupling. However, if you have to apply attributes to all the methods you wish to intercept, you’re effectively creating a hard dependency on the aspect you’ve applied. Let’s say that you want to handle security on a certain set of methods in an aspect. The moment you declare a security-attribute on your methods you’re creating a hard dependency on that security implementation. Furthermore, you’re also creating a hard dependency on the AOP-framework itself, which could lead to vendor lock-in. DI interception, on the other hand, allows unobtrusive interception because you configure your aspects in your DI-container.
- Attribute-based interception is defined at compile-time. This means that you can’t change your mind once your solution has been compiled. DI-interception is dynamic and it could be depend on configuration, but also on runtime data.
- Attributes in .NET are required to have a simple constructor. This means that your aspects cannot have dependencies. Therefore your aspects can only use what is readily available (this is limited to the global scope, and the local scope of the method it’s intercepting). With a DI-container you can define dependencies for your aspect and they can be resolved the same way your normal dependencies are resolved. You could say that AOP provides a degree of loose coupling to its clients, but internally it limits the possibility to loosely couple the aspects.
- Attributes can only be applied to a limited set of members (parameters, members, properties, fields, types, etc. With DI-injection an aspect can be executed based on conventions.
In a blog post on AOP versus Dependency Injection Gael Fraiteur (lead developer of the PostSharp AOP framework) mentions the following disadvantages of DI:
It only works on the surface of components, exposed as an interface. It does not work for all methods that are internal to the component, whether public or private, static or instance. Remember that a component is an application part, and generally includes several classes.
I think this is actually an advantage. Applying interception to private members is a very bad practice and shouldn’t be allowed under any circumstance. If you allow interception on private members, you’re completely bypassing the intention of declaring a private member. It would allow encapsulation to be broken by an external source. As much as we would like to handle cross cutting concerns in an unobtrusive way, encapsulation should never be touched. Allowing interception only on specified interfaces, forces you to declare interception points explicitly.
It only works when the client code got a reference of the component from the container. Aspects will not be applied if the component invokes itself, or if the component is invoked without the intermediary of the container. This can be seriously misleading!
Along the same lines as the previous point, I think that intercepting the inner workings of a component or a class is a bad idea; that is, when you’re intercepting calls from the component calling itself. In the other situation, where the component is invoked without the intermediary of the container, there is a valid point. However, if you’re using DI correctly, this situation should not present itself. If you see yourself obtaining instances outside of the container often, you should ask yourself whether you’re actually using your DI-container correctly.
Since aspects are applied at runtime, there is no possibility for build-time verification. Build-time AOP frameworks such as PostSharp will emit compilation errors if the aspect is used incorrectly, and will even provide the aspect developer with different ways to verify, at build time, that the aspect has been used in a meaningful way (for instance, don’t add a caching aspect to a method that returns a stream, or to a method that already has a security aspect).
Since aspects are just requirements (albeit another type of requirement), I think compilers can do little to nothing to ensure the correct use. After all, these requirements change from application to application and they should be verified by good unit tests, which is possible with DI
It is difficult to figure out (a) which aspects are applied to the piece of code you’re working on and (b) to which pieces of code the aspect you’re working on has been applied. Full frameworks such as PostSharp or AspectJ can display this information inside the IDE, substantially improving the understandability of source code.
This is another example of the tight coupling that AOP introduces. You can compare this with the “disadvantage” you get when you start programming against interfaces. At first, you’ll notice that you can’t just use the “go to definition”-command in Visual Studio anymore. Is this a disadvantage? To some degree, yes. But on the other hand you get a more flexible code base of which you can vary the behavior by external factors (such as configuration).
Run-time AOP has higher runtime overhead than build-time frameworks. This may be of minor importance if you only apply aspects to coarsely-grained components, but just generating dynamic proxies has a significant cost if you have hundreds of classes.
This is a valid point. Runtime overhead can be a prohibitive disadvantage in certain situations.
Dynamic proxies are always a side feature of dependency injection framework and do not implement the full vision, even when technically feasible. They are mostly limited to method interception, which hardly compares to what AspectJ and PostSharp have to offer.
This is a non-argument. The fact that it’s not the main feature of dependency injection, doesn’t make it bad. In my opinion it just underlines that IoC is the most flexible solution to dependency management.
After that, he also mentions that DI interception could be seen as a hype and therefore overused. I think he’s correct in saying that any feature shouldn’t be used just for the sake of using it or it being new. However, I don’t think this is a specific disadvantage of DI-interception compared to AOP frameworks. Even more so, I think the danger to overdo it is a lot bigger with an AOP-framework.
Although AOP-frameworks certainly have their value in certain types of applications (a code inspection framework to name one), I feel DI interception is usually the better choice.
To me, the biggest difference between the two methods is that DI-interception embraces OO practices. In the heart, DI interception is just good OO-design. AOP on the other hand tries to fight OO and uses post compilation. It steers away from concepts like encapsulation and polymorphism (to a certain degree).
Although aspects are definitely interesting for solving cross cutting concerns, I still think your domain should be captured in an OO-model. Interception should just be a method to let your domain model shine even more and I think DI interception accomplishes that better, whereas AOP tries to force the domain model into a different concept.