Wednesday, October 28, 2009

Spring.Net and AOP

When dealing with cross cutting concerns in your application (logging, exception handling, performance monitoring, etc) you want to do it as unobtrusive as possible. AOP (Aspect Oriented Programming) helps you with this by using attributes, code weaving or dynamic proxies to dynamically add cross cutting functionality throughout your application.

Attributes are known to be quite slow. Code weaving is a very popular technique but unfortunately stack traces you receive from debugging or logging is not accurate because of the code being injected at run time. Thus I lean more to dynamic proxies.

I recently took a look at Spring.Net to see what they have to offer. Spring.Net uses dynamic proxies to implement their AOP solution. They implement AOP using Advice and Aspects. An advice can be created in separate assemblies and by using configuration can apply the advice to any object in your domain.

Spring.Net has 4 built in advice types you can use:
  • Before - applied before method call
  • After - applied after method call
  • Around - applied before and after method call
  • Throw - applied when an exception is raised (you defined which exception types you want advice to be applied)
I created a POC using the Around advice to apply some performance monitoring to my method calls so that I can easily spot bottlenecks occurring in production.

I created a separate assembly which will contain all my commonly used Aspects:

public class PerformanceLoggingAroundAdvice : IMethodInterceptor
{
public object Invoke(IMethodInvocation invocation)
{
Stopwatch stopwatch = new System.Diagnostics.Stopwatch();

// Start timing the method
stopwatch.Start();

// Execute the method
Object returnValue = invocation.Proceed();

// Stop timing the method
stopwatch.Stop();

// Log method runtime
Console.Out.WriteLine("ExampleAroundAdvice: '" + invocation.Method.Name + "' took " + stopwatch.ElapsedMilliseconds + " milliseconds to run.");

return returnValue;
}
}


Next, I created my Model which will contain my entities and domain services. I am going to apply my advice to my services. Note that the service has no Spring.Net specific information at this point - which keeps your domain nice and clean.

public interface IExampleService
{
Object GetObject(long id);
void SaveObject(Object obj);
void RunOperation(Object obj);
void RunOperationWithError(Object obj);
}


 public class ExampleService : IExampleService
{
public Object GetObject(long id)
{
// Run some logic to retrive an object
return "testObject";
}

public void SaveObject(Object obj)
{
// Run some logic to save an object
Thread.Sleep(100);
Console.WriteLine("saved the object");
}

public void RunOperation(Object obj)
{
// Run some logic
Thread.Sleep(50);
Console.WriteLine("ran an operation");
}

public void RunOperationWithError(Object obj)
{
// Run some logic and throw an error...
throw new ApplicationException("This method threw an error!!!");
}
}


Because Spring.Net uses proxy generation for its AOP, whenever you need to create an new object to which advice will be applied you need to ask Spring.Net to create it for you. Spring.Net will use you class definition and combine it with any configuration information specified for it. Because you dont want Spring.Net code everywhere throughout your code it is best to create a Factory which is a nice pattern to abstract the object creation for you.

public class Factory
{
public static IExampleService CreateService()
{
IApplicationContext ctx = ContextRegistry.GetContext();
IExampleService exampleService = (IExampleService)ctx.GetObject("exampleService");
return exampleService;
}
}


Next we have our client application which will use your domain. This is where the configuration will be placed. Below is the configuration for the advice and to which object it is applied and also to what methods you want to apply them.

<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>

<spring>

<context>
<resource uri="config://spring/objects" />
</context>

<objects xmlns="http://www.springframework.net">
<description>An example that demonstrates use of a poincut.</description>

<object id="PerformanceMonitoringAroundAdvisor" type="Spring.Aop.Support.NameMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<object type="SpringExample.Aspects.PerformanceLoggingAroundAdvice, SpringExample.Aspects" />
</property>
<property name="MappedNames">
<list>
<value>*GetObject</value>
<value>*SaveObject</value>
</list>
</property>
</object>

<object id="exampleService" type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="Target">
<object type="SpringExample.Model.ExampleService, SpringExample.Model" />
</property>
<property name="InterceptorNames">
<list>
<value>ConsoleAroundAdvisor</value>
<value>PerformanceMonitoringAroundAdvisor</value>
</list>
</property>
</object>

</objects>

</spring>


Our calling application will then use our domain:

class Program
{
static void Main(string[] args)
{
try
{
IExampleService exampleService = Factory.CreateService();

Console.WriteLine("Example: Get Object");
exampleService.GetObject(25);

Console.WriteLine("\nExample: Save Object");
exampleService.SaveObject("testSaveObject");

Console.WriteLine("\nExample: Run Operation");
exampleService.RunOperation("testRunObject");
}
catch (Exception ex)
{
System.Console.Error.WriteLine("Error occured: " + ex.Message);
}

Console.WriteLine("\nPress enter to continue...");
Console.ReadLine();
}
}


As you can see there are numerous advantages to using Spring.Net. Some disadvantages/drawbacks worth noting is that all service to which you want to apply advice needs to have an interface. Second, as seen above, Spring.Net is quite XML heavy so the maintenance of the XML could become problematic. It would be best to use an nice XML editor to maintain the XML.

Hope this has been helpful. Try it out and enjoy.

2 comments:

  1. Your POC is a typical "HelloWorld" ramp-up to using aspects, and that is good. You got good ROI very quickly. But as you dabble more, you will realise that AOP is not the same as OOP. It is a different way of dealing with problems.

    For infrastructural cross cutting concerns, keeping them in separate assembly is fine. However, when you start solving domain problems in terms of concerns, then do look at keeping the aspects closer to the concerns that they cut across.

    Also, it places higher imperative on TDD. Untested aspects are dangerous. More importantly, you need to figure out a way to test your pointcuts too.

    Finally, be wary of pointcut fragility ... it's a sign of design fragility.

    ReplyDelete
  2. Thanks Aslam. Good advice. We are currently evaluating different technologies to solve our cross cutting concerns - so far have taken a look at Spring.Net and PostSharp. Any other good ones that you know of?

    Appreciate the comments. Keep them coming :)

    ReplyDelete