On this blog I will regularly post my thoughts related to Software Engineering, in general as well as .NET specific

December 28, 2009

Spring.NET AOP - behind the scenes (1)

On the forums I see one topic coming up quite often: "Can I advise XY?". In this multipart post series I will describe the motivation for AOP and the way Spring.NET AOP (and most others like Castle.DynamicProxy and LinFu) technically work in .NET to give you a better understanding and provide you the knowledge to help answering such questions yourself.

The example: Retrying operations

Instead of the usual logging example I'd like to show you another useful feature: Retrying operations. For the sake of simplicity let's assume we're calling a webservice method for calculating the sum of two integers.

Since webservices usually involve calling over the network, they are inherently unsafe and might fail for various reasons. In our application, we would like to retry webservice operations 3 times before giving up, with a 1 second delay between retries.

There are a couple of ways to implement this requirement, the most direct approach probably by deriving our own class from the webservice client class:

When implementing this requirement across all our webservice clients we not only end up scattering the same lines of code all over our codebase. A change in the requirement might cause us having to change all our webservice client classes causing a huge amount of work.

A manually implemented a solution

A structured way for non-intrusively adding behavior to exisiting code is described in the GoF book, the pattern is called "Decorator", the basic idea being wrapping the actual target method with additional code. Thus you do not need to modify any existing code, instead you "chain" the various required behaviours, each behaviour implemented in its own class. In contrast to the GoF-pattern, nowadays composition is favoured over inheritance, thus instead of using the inheritance approach, let's introduce an interface to easily chain our behaviors:

publicinterface ICalculator
{
int Add(int x, int y);
}

ICalculator calc = ...;
int sum = calc.Add(2, 5);

Now we can easily implement our business logic and the infrastructure constraint separately and chain them later as needed:

Notice how the CalculatorRetryDecorator delegates the actual work to the next calculator in the chain. Now, whenever our requirements force us to retry calculator operations, we just "chain" the implementations together:

Now, when we discover that our performance is to slow, well - implement a caching decorator that caches method results for a certain amount of time using the same approach and add it to the decorator chain: