This article originally appeared on Sept. 15, 2015.
Most application development will contain common functionality that spans multiple layers, such as authentication, communication, exception management and logging. Such functionality is generally thought of as a cross-cutting concern because it affects the entire application and should be centralized in one location. Unfortunately, though, how many times have you seen code modifications like this?
The business requirements that are being satisfied here are:
- If an exception is thrown, log information about the exception at the source
- Performance metrics on how long a synchronous method takes to complete
This code usually starts in a handful of places but inevitably is copied and pasted throughout the application as it’s easier than architecting a solution that would be more manageable.
This approach works. However, this creates some rather troubling issues:
- The exception and performance code are now tightly coupled to the work being done and also to each other.
- Any changes to the way exceptions are logged and timing calculated would need to be replicated in every piece of code.
- The timing code will always fire and cannot be removed unless the code is removed and redeployed. It would be great if we could remove the timing code via configuration when we deploy to production so we don’t impact performance.
How can we align the implementation with better practices? Aspect-Oriented Programming (AOP) to the rescue.
What is Aspect-Oriented Programming?
The goal of AOP is to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding behavior to existing code without modifying the code itself and, in C#, is usually done with a class or method attribute. This allows behaviors that aren’t critical to the business logic to be added without repeating code or creating tightly coupled code.
To maintain the business requirements, we can use our existing IoC container, Castle Windsor. If you’re using a different container, you should be able to extract this approach to suit your needs, assuming it supports interception. If you’re not familiar with Castle Windsor, you’ll want to learn more about Castle Windsor interception.
Let’s break down what’s going on here.
- At the context root, we’re setting up Castle Windsor by using an installer that’s registering a concrete implementation to requests for an implementation of IProjectData interface.
- We then get the instance from Castle Windsor and call Run(), which calls the GetAllItems() method on the interface and displays the results in the console.
- If we run the application, the output should be:
How did we get that data? Looking at the installer, we can see we’re resolving the dependency IProjectData with the implementation of ProjectDataLocal. Visit GitHub to learn more about Castle Windsor installers for registering dependencies.
To keep things simple, ProjectDataLocal is a hard-coded list of items we return, but could just as easily call into a database.
So far, so good. But now we need to update the code to handle the business requirements. Of course, we could fall back into bad habits and do something like this:
But we can do better … and since we have Castle Windsor already in place, it becomes rather simple.
Let’s first create an Aspect class that will handle two ideas:
- We need an attribute to decorate a class or method to indicate that we want to fire the aspect invocation at run time. This is accomplished by inheriting from the Attribute class.
- We need to perform a check to see if the calling class or method has the attribute. This is handled in the CanIntercept() method, which puts the code in one place to avoid duplication.
This is an abstract class, which lets us do two things. First, it requires an implementation to be instantiated so we can create and call abstract methods that must be present in the concrete class (ProcessInvocation, for example). Second, it will check the class and method for itself, since it’s also an attribute, to determine if it should process the invocation.
For our application, we’ll need two implementations to handle both business requirements. We could, of course, combine these into one, but then we’d be stepping backward and violating single responsibility.
To create an implementation, all we need to do is inherit from Aspect and implement its one abstract method, ProcessInvocation().
Each aspect is writing to the console just to keep the example simple. In a real application, it would call the logging implementation.
To wire this, we need to modify our installer file and register the interceptors:
The first thing required is to register the interceptors with themselves. Then we can attach them to our IProjectData by type.
When Castle Windsor resolves the dependency, it will inject these into the stack and call the interceptors in succession, and THEN call the method.
The basic flow looks something like this:
Notice we placed the TimingAspect last so that we get more accurate performance information on GetAllItems().
If we run our application at this point, we won’t see any changes since we haven’t set the attributes to our implementation, so let’s do that now:
And our output where we can see the elapsed time printed on the screen:
Now, if our application was to throw an exception, we’d expect our output to be:
Now that we have our AOP solution in place, we can start to add configuration-based execution.
Let’s say a new business requirement is added, which requires us to remove TimingAspect from executing in production due to its overhead. For this, we head back over to the installer file and make a change.
As you can see, we always intercept ExceptionAspect for the IProjectData dependency, but we only intercept TimingAspect if we have a configuration that has a key set to True. This allows us to move our execution behavior to the configuration where we can selectively turn things on or off without having to recompile the application.
AOP is a clean way to selectively target method wrapping, and using an IoC container makes short work of the implementation. We’ve set up the ability to easily add new aspects we can selectively use in our code to meet business needs. So, the next time you start to copy and paste code similar to what you see here, stop and think about if AOP is the right fit for your application.