Introduction
A brief comparison of fundamental AOP techniques
AOP

A brief comparison of fundamental AOP techniques

In my previous post “Why choose DI Interception over Aspect Oriented Programming?” I explained my preference for DI interception over AOP. I received a lot of comments on this (especially on reddit) which pointed out some errors. In this article I want to reiterate these points and provide a better explanation. I considered adapting the previous post, but in the end I decided to create a new post and keep the old one for reference (a note has been added to the other post).

The conclusion of my other post was that I consider DI interception better than AOP for various reasons. The problem with the post in general was that I based my entire discourse on reference implementations in .NET (DI containers in .NET and PostSharp for AOP). I now realize that this is incorrect. DI interception IS aspect oriented programming. After reading the comments and rethinking my points I realize that I really should have made a distinction between the implementation of various AOP functionalities.

Intercepting calls

The first distinction is how AOP framework intercepts calls. There are two methods for doing this:

  • Injecting code at compile-time
  • Using the decorator pattern with runtime generated classes that wrap around the request. (also called dynamic proxies)

The way AOP works is by intercepting a call (to a  method, property, …) and executing some code before and/or after that call.

One way to do this is through code injection. One of the most popular frameworks in .NET, PostSharp, implements call interception by performing a post-compile step and injecting the code into the generated MSIL.

A second method of implementing call interception is by using the decorator-pattern and runtime generated classes. These classes are called dynamic proxies. This is how most of the DI-containers implement call interception.

I tend to favor the decorator pattern because of the following advantages:

  • Flexibility: Since code injection is done at compile-time, by definition you cannot alter your aspects at runtime. The dynamic generation of decorators at runtime, on the other hand, provides a lot more flexibility. Since it’s done at runtime, you can effectively base your interception on configuration and/or runtime data. If you want to change the behavior of your application, you don’t need to recompile.
  • Debugging: Sine code injection effectively changes your code, the generated MSIL (or bytecode in JAVA) is different from what you may have in your source-files. This can cause problems when you are debugging. Most AOP-frameworks have solutions for this, but it can sometimes be a problem.
  • Because the decorator pattern is based on interfaces, it forces us to explicitly define interception points. This constraint is sometimes looked at as a disadvantage, but I think it’s a good practice. With code injection, an interception could potentially take place anywhere, even in private methods. This breaks encapsulation.

However, compile-time injection has some advantages over dynamic generation as well:

  • If you get a reference to an object without the dynamically generated wrapper (with a DI scenario, that would be outside of the DI-container), no interception will be applied. With compile-time code injection, the actual code is modified, so there’s no way to circumvent it, However, if you see yourself regularly obtaining references to objects to classes without the dynamically generated wrapper, you should probably revise the way you are obtaining these references and consider whether you are not creating hard dependencies on those classes.
  • Since code injection is done at compile-time, it will perform better at runtime. In contrast, to generate the dynamic proxies, reflection and dynamic code emission is needed. This could potentially slow down your application.
  • Compile-time injection can** inject code everywhere**, even in the middle of a method. This is not possible with generated proxies. There are probably situations where this might come in handy, but as a general rule, I think this is a bad idea, since it will make your code behave unexpectedly.

In my opinion, unless you really have performance issues, the best way to do call interception is through dynamically generated decorators. It provides more flexibility because you can alter behavior at runtime.

Configuring interception

The second distinction is the configuration, in other words the way you apply an interception to a piece of code. There are also two methods for doing this:

  • Through attributes on methods
  • Configuration (either with code or via XML)

When you want to configure interception there are two method do this, The first one is through the use of attributes. This is an example in c#:

[Log] public void doSomething(string input) { // do something here }

The attribute on the method tells the framework that this method should be wrapped with logging statements (usually logging input and output).

Another method of applying interception is through either XML or code. With a DI-container this configuration in code will sit in the composition root. This is an example of how to configure an interceptor with Ninject:

var binding = kernel.Bind().To(); binding.InterceptBefore().With();

In the above snippet the interface ISomething is bound to the concrete class Something. After that, the container is configured to call the LogInterceptor-class before executing the request*. *This class implements a specific Ninject interface which gives you information on the context (the method being called, the parameters, …). You can imagine this configuration easily being moved over to an XML-file. A similar approach is available for other DI containers. (note that certain DI containers also allow attribute-based configuration).

The code/xml configuration paradigm offers a few advantages over attribute-based interception:

  • By applying an attribute you’re creating a direct dependency on the interception. Since AOP is about** loose coupling**, you immediately lose this advantage.
  • Since the attribute is close to your code, it’s very tempting to create a dependency on the attribute.  If you need external information, this should be provided by injection, not interception. This anti-pattern is obviously also possible with a configuration based interception, but since this interception is not as visible it’s less tempting to introduce these kind of dependencies.
  • When you’re applying attributes at compile-time you’re hard-wiring the application. There’s no way you can remove/apply other interceptors based on configuration or on runtime data.
  • In .NET, attributes 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 configuration in the DI-container or in XML you can define dependencies for your aspect and they can be resolved the same way your normal dependencies are resolved.
  • Attributes can only be applied to constructors, methods, properties and parameters. With a configuration approach you could base your interception on anything else (i.e. the name of a method, the time of the day, …)
  • It is very difficult to test the application. If you use code configuration, unit testing is easy and can be done in the composition root.
  • If you want to intercept various method calls, you’d need to apply the attribute to all the methods separately. This is not** DRY**. With a configuration, you define and maintain your interceptions in one place.
  • Since we’re talking about configuration, you could possibly introduce some conventions and then apply interception based on convention (following the convention-over-configuration paradigm).

**
Attribute based interception** also offers a few advantages over configuration-based interception:

  • When you have very complicated interceptions, it’s sometimes hard to see what your code actually does. Since attributes sit right at the code where it’s applied it can be easier to figure out what exactly the code does.
  • There’s no tooling support or compile-time support for configuration based interception. This is impossible because the interception is only applied at runtime.

Because the configuration approach offers a higher degree of loose coupling and more flexibility, I prefer code/xml configuration over attribute-based configuration. Under certain circumstances attribute-based configuration could be useful. This is true when every interception is similar but still different. (for example, ASP.NET MVC has the Authorize-attribute which receives a parameter to determine which users/groups are allowed).

Conclusion

When you’re looking into managing cross cutting concerns with an aspect oriented approach you will have to pick a framework. Frameworks have different strategies to resolve the configuration and the interception of calls. While certainly every framework may offer other benefits than just the advantages I described above, in my opinion, these two are fundamental to choose a framework. For the reasons described above, I usually apply AOP through my DI-container. Most DI-containers (at least in .NET) offer dynamic proxies and either code or XML-configured interception.

It is certainly true that a framework such as PostSharp offers a lot more options for interception and can change your code more profoundly. However, I think AOP should go hand in hand with a good OO-design. AOP can get really difficult to understand and maintain once it is overused. I think it should be used only for resolving cross cutting concerns such as logging or security. Once you start moving your domain model / logic into AOP-classes you’re probably going one step to far. That being said, you’d need to look at your specific requirements and find what suits better in the given situation. I hope in this blog post I haven been able to explain some of the differences and what consequences they might bring.

Kenneth Truyers
View Comments
Next Post

Flexible and expressive unit tests with the builder pattern

Previous Post

Why choose DI interception over aspect oriented programming?