Introduction to Aspect Oriented programming with DynamicProxy

So what’s AOP yo!?

Several months ago I read Bob Martin’s Clean Code (which by the way is a phenomenal read if you take the time to understand the concepts, and even better if you challenge yourself to apply them. :-p) and was intrigued by his review of Aspect Oriented programming.  Most of our applications have a core function which is usually entangled with other concerns (also called cross-cutting concerns or aspects) such as: security, caching, exception handling, logging, etc.   In using AOP you use a few tools to write things like caching once in your application and reuse that code all over the place without the handy ctrl-c + ctrl-p combo.  In this brief post I’ll demonstrate how you can implement AOP in your application by using StructureMap (I’m assuming you already know how to use an IoC container) and Castle project’s DynamicProxy.

Where do I start?

(Warning: I’m assuming your familiarity with StructureMap (or your tool of choice) so I won’t get into the guts of it’s setup.)

StructureMap has this neat feature that you can use when you’re wiring up your dependencies called “EnrichWith”.  Basically it allows you to decorate a class with a wrapper of your choice.  You can manually create a wrapper or in our example we’ll use DynamicProxy to do this grunt work for us.  Here’s our problem: We want to cache several methods in our service layer or in a repository, but we don’t want to copy/paste our 30 lines of caching code into 20 different methods.

  1. We need to create a custom attribute which will be used by the interceptor to know if it should use our custom caching code or not.
  2. We implement IInterceptor which is an interface from the DynamicProxy library.  This guy will intercept the method calls, we’ll see the value in the later.
  3. Finally we need to Enrich our service class with a proxy that has this new interceptor and that’s it!

Here’s my custom attribute:

[AttributeUsage(AttributeTargets.Method)]
    public class CacheMethodAttribute : Attribute
    {
        public CacheMethodAttribute()
        {
            // default
            SecondsToCache = 10;
        }

        public double SecondsToCache { get; set; }
    }

Next, here’s our Caching Interceptor:

public class CacheInterceptor : IInterceptor
    {
        private static object lockObject = new object();

        public void Intercept(IInvocation invocation)
        {
            CacheMethod(invocation);
        }

        private void CacheMethod(IInvocation invocation)
        {
            if (! IsMethodMarkedForCache(invocation.Method))
            {
                invocation.Proceed();
                return;
            }
            var cacheDuration =
                (invocation.Method.GetCustomAttributes(typeof (CacheMethodAttribute), true).First() as
                 CacheMethodAttribute ?? new CacheMethodAttribute()).SecondsToCache;

            var cacheKey = GetCacheKey(invocation);

            var cache = HttpRuntime.Cache;
            var cachedResult = cache.Get(cacheKey);

            if (cachedResult == null)
            {
                lock (lockObject)
                {
                    if (cachedResult == null)
                    {
                        invocation.Proceed();

                        if( invocation.ReturnValue == null ) return;

                        cache.Insert(cacheKey, invocation.ReturnValue, null, DateTime.Now.AddSeconds(cacheDuration),
                                     TimeSpan.Zero);
                    }
                }
            }
            else
                invocation.ReturnValue = cachedResult;
        }

        private bool IsMethodMarkedForCache(MethodInfo methodInfo)
        {
            return methodInfo.GetCustomAttributes(typeof (CacheMethodAttribute), true).Any();
        }

        private string GetCacheKey(IInvocation invocation)
        {
            var cacheKey = invocation.Method.Name;

            foreach (var argument in invocation.Arguments)
            {
                cacheKey += ":" + argument;
            }

            return cacheKey;
        }
    }

This is where all the magic happens. When the method is decorated appropriately and is called it first hits this interceptor which has a hook into the “invocation”.
It’s this invocation variable that has details about the method call made, like: arguments, method name, attributes on the method, and many many more. It’s here where we place our caching code
and then if we actually don’t find the values in cache we call

invocation.Proceed();

which sends control to the wrapped method, it makes it db call and when it returns the control is returned to this interceptor, so the next statement that is executed is

if (invocation.ReturnValue == null)...

If we actually found that value in the cache and didn’t have to make a call back to the db, then we insert that cached value into the ReturnValue and that’s it.
And finally, let’s wire up this puppy:

var dynamicProxy = new ProxyGenerator();

            Scan(assemblyScanner =>
                     {
                         assemblyScanner.TheCallingAssembly();
                         assemblyScanner.WithDefaultConventions();

                         ForRequestedType()
                             .EnrichWith(z => dynamicProxy.CreateInterfaceProxyWithTarget(z, new ExceptionInterceptor()))
                             .TheDefault.Is.OfConcreteType();
                     }
                );

So now that we have this code in place, all that we have to do is decorate any methods that we want with our custom attribute, and make sure that they are Enriched in our IoC wireup and we get caching for free!

        [CacheMethod(SecondsToCache=60)]
        public IList SearchJobs(Criteria criteria, int page, int pageSize)
        {
            var results = _myService.SearchJobs(criteria, page, pageSize);
            return results;
        }

That’s it!  That’s all our method that needs caching has to do.  You’ll notice that we no longer have to copy/paste our “caching” routine into every single method that requires caching, but instead we just use this simple attribute.

So what are the Cons?

  • There is a slight performance degradation due to the use of reflection, but in my opinion Developer performance is far more expensive than extra cycles on a machine.  Remember that Dell 2300x (fake) server doesn’t ask for vacation, get moody or require health insurance.
  • There is certainly a learning curve for noobies.  In fact it looks plain scary at first, but I promise you, use it and when you do this new tool will become quite handy.
  • I’d recommend using this on most (if not all) enterprise apps, console, web, etc), but I’d stay away from parsers that have to crunch things incredibly fast.
Advertisements
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: