Adding Business Layer Caching Using Castle Windsor AOP
Aspect Oriented Programming (AOP) is a programming paradigm that allows separation of cross-cutting concerns. But you probably already knew that if you’re reading this. If not, go have a read at the Wikipedia page on AOP or the introduction page on the Castle Project site.
The Problem
If we have an existing application to which we want to add caching, the traditional approach is to include caching calls within the app itself.
We’re going to start off with a simple app to read stock prices and then add caching to it. The service call looks like this:
Simple enough – read some JSON from an API, wrap it up and return it.
If we were going to add caching directly to this method, we’d probably do something like this:
Cache Provider
Rather than using cache directly, we’d be better creating a cache provider interface and injecting it using Dependency Injection.
Updated Code
This works well enough. The problem is that the caching implementation is sitting right there in the method and will have to be repeated for every method that we want to be cached. Then, if we want to change the way the logic works, that could mean editing many files. So, not ideal.
We’ll write our caching implementation using interception and without altering the original files.
As it says in the description, dynamic proxies are a way of creating a lightweight wrapper around an object to intercept and alter its behaviour.
Interceptors
In Windsor, Interceptors take advantage of Dynamic Proxies to alter the functionality of a call. We can use this to wrap calls to our GetStockReading method with caching.
Here’s our Interceptor class in full:
There’s a fair bit going on here, so let’s take it a bit at a time.
Class Definition
Our interceptor implements the IInterceptor interface which has the single method void Intercept(IInvocation invocation). We’ll get to this below.
Constructor
We’re using dependency injection to pass in instances of the objects and values our Interceptor needs.
We have an implementation of ICacheProvider that we defined above, a Boolean to indicate whether caching is enabled and the cache timeout to use in seconds. Each of these are saved to local variables.
Intercept
This is the code that actually intercepts and manipulates that call to our method so we’ll break it down further.
First we check to see if caching is enabled. We also check to see what the return type of the called method is – if it’s void then there’s nothing to cache.
If caching is disabled or the return type is void, we just tell the original call (the invocation) to proceed as normal and exit the interceptor.
If we get beyond that, we’re going to be interacting with the cache. So we need to construct a unique key for the cache object for lookups.
The BuildCacheKeyFrom(invocation) just takes the name of the called method and appends the parameters to it. This would need to be refactored and cleaned up for a larger application but it’ll do for our purposes for the moment.
Once we have a key, we check the cache to see if that item already exists. If so, we set the return value of the method to that and exit. This causes the intercepted method to complete and return the value we’ve just set.
If the cache didn’t contain a value, we need to run the method as normal then cache the result.
Before hooking up anything with the interceptors, our installer looked like this:
All we’re doing is specifying the implementation for IStockServices.
To that, we need to add our cache provider:
The implementation of WebCacheProvider is very simple:
We also need to add a registration for our Interceptor:
If you remember, our constructor for CacheInterceptor, it looked like this:
The values for cachingIsEnabled and cacheTimeoutSeconds are read in from web.config using the Dependency.OnAppSettingsValue(string) in the installer. These values are just set up like so:
The last step is to tell Windsor to use the Interceptor for IStockServices. So our full installer code becomes:
And that’s it. Any calls to a method of IStockServices will be wrapped in the interceptor.