tsJensen

A quest for software excellence...

Aspect Oriented Programming Not Worth It

I have said before that I like Uncle Bob's way with words. This includes his Clean Code Discussion:

"When aspects first came out, I was intrigued by the idea. But the more I learned, the less I liked it. The problem is that the pointcuts are tightly coupled to the naming and structure of the code. Simple name changes can break the pointcuts.  This, of course, leads to rigidity and fragility. So, nowadays, I don't pay a lot of attention to AOP. 

"For my money, the OO techniques that I demonstrated for logging can be pretty easily used for many other cross-cutting concerns. So the tight name-coupling of AOP isn't really necessary."

I too once thought that AOP was a great idea. A carefully crafted AOP solution can even overcome some of the coupling issues that Uncle Bob mentions. But eventually the dependency entanglements even with nice clean injection techniques, pick your poison, you end up with a hodge-podge of rigid and fragile. Eventually you find yourself writing code to serve your AOP solution rather than your use case.

My money is with Bob.

DomainAspects: Aspect Oriented Programming with Castle Windsor Interceptor

Last week I released DomainAspects 2.0 without many details other than its general features. In this post and future posts, I’m going to delve into the details one area or feature at a time. My purpose is to help you understand how each piece works and provide examples of how you might use the library or the code in your own projects.

The few aspect oriented programming (AOP) languages and frameworks that I have looked at seem to me to be unwieldy and unnecessarily intrusive in the the daily workflow of an application developer, cluttering up the code and creating cumbersome dependencies on the AOP construct itself. Fortunately there is a cleaner alternative to AOP languages found in the inversion of control (IoC) containers we so commonly use for dependency injection in order to enjoy the benefits of SOLID Design: the Interceptor.

Interception is supported by many of the IoC libraries. I’ve used Unity a little but most of my experience is with Castle Windsor, so of course, I prefer Windsor. I also prefer Windsor’s interception approach because I find it easier. If you are a Unity user, take a look at Dino Esposito’s Unity Interception article on MSDN. You an see for yourself that the Unity IInterceptionBehavior implementation is relatively more complex than Windsor’s IInteceptor:

public interface IInterceptor
{
   void Intercept(IInvocation invocation);
}

In DomainAspects, I wanted my Interceptor to handle logging and exception handling, which I like to call “auditing”, probably the most common of the cross cutting concerns handled by AOP. I also wanted the Interceptor to handle authorization security. But I wanted to have a simple Interceptor class that would have its “auditor” and “authorization” dependencies injected by the same container. Each dependency would handle its specific responsibilities.

Here’s the Windsor fluent configuration code for each of these. Note the first component is the IPrincipalProvider which is a dependency of the IOperationAuthorizer implementation. I then register the components for the authorizer and the auditor. The last component configured by the DomainAspects ServiceFactory class is the OperationInterceptor.

_container.Register(
   Component.For<IPrincipalProvider>()
      .ImplementedBy(_principalProviderType)
      .Named("PrincipalProvider")
      .LifeStyle.Transient,

   Component.For<IOperationAuthorizer>()
   .ImplementedBy(_operationAuthorizerType)
   .Named("OperationAuthorizer")
   .LifeStyle.Transient,

   Component.For<IOperationAuditor>()
   .ImplementedBy(_operationAuditorType)
   .Named("OperationAuditor")
   .LifeStyle.Transient,

   Component.For<OperationInterceptor>()
   .Named("OperationInterceptor")
   .LifeStyle.Transient);

When the DomainAspects ServiceFactory’s Create method is called, if the service has not already been registered with the Windsor container, it will register the container and wrap it with the Interceptor we have just configured using Windsor’s fluent interface. Here’s the code:

_container.Register(
   Component.For(interfaceType)
   .ImplementedBy(implementedByType)
   .Named(implementedByType.Name)
   .Interceptors(InterceptorReference.ForKey("OperationInterceptor")).Anywhere
   .LifeStyle.PerThread);

Note that the .Interceptors method takes a params of InterceptorReference objects. So like Unity, you can chain interception. Unlike Unity, the chaining occurs automatically for you in sequence of the array. Your IInterceptor implementation does not require you to be concerned with wiring up the chain as does the Unity IInterceptorBehavior.

Now take a look at the relatively simple, application requirement focused OperationInterceptor class which implements the Windsor IInterceptor interface with a single method and depends on the container to inject its two dependencies. The code in the interceptor class here just handles the business case of examining the class and method being intercepted for attributes that will require action by one or both of its dependencies.

public class OperationInterceptor : IInterceptor   
{
   private IOperationAuditor auditor;
   private IOperationAuthorizer authorizer;

   public OperationInterceptor(IOperationAuditor opAuditor, IOperationAuthorizer opAuthorizer)
   {
      auditor = opAuditor;
      authorizer = opAuthorizer;
   }

   public void Intercept(IInvocation invocation)
   {
      AutoLogAttribute autoLogAttribute = null;
      AuthorizeAttribute authorizeAttribute = null;
      var methodInfo = invocation.MethodInvocationTarget;
      object invokedKey = null;
      try
      {
         //find AutoLogAttribute
         autoLogAttribute = ((AutoLogAttribute[])methodInfo
            .GetCustomAttributes(typeof(AutoLogAttribute), true))
            .FirstOrDefault();

         if (null == autoLogAttribute) //try class level
            autoLogAttribute = ((AutoLogAttribute[])methodInfo.DeclaringType
            .GetCustomAttributes(typeof(AutoLogAttribute), true))
            .FirstOrDefault();

         if (null != autoLogAttribute &&  null != auditor)
         {
            //log invocation
            auditor.LogOperationInvoked(methodInfo, 
               invocation.Arguments, 
               invocation.GenericArguments, 
               out invokedKey);
         }

         //find AuthorizeAttribute
         authorizeAttribute = ((AuthorizeAttribute[])methodInfo
            .GetCustomAttributes(typeof(AuthorizeAttribute), true))
            .FirstOrDefault();

         if (null == autoLogAttribute) //try class level
            authorizeAttribute = ((AuthorizeAttribute[])methodInfo.DeclaringType
            .GetCustomAttributes(typeof(AuthorizeAttribute), true))
            .FirstOrDefault();

         if (null != authorizeAttribute && null != authorizer)
         {
            //authorize
            authorizer.Authorize(methodInfo, invocation.Arguments, invocation.GenericArguments);
         }

         //proceed with invocation
         invocation.Proceed();
      }
      catch(Exception e)
      {
         //log exception and throw
         if (null != autoLogAttribute && null != auditor)
         {
            auditor.LogOperationException(invokedKey, methodInfo, e);
         }
         throw; //always throw to bubble up exception and preserve call stack
      }
   }
}

If you are considering aspect oriented programming or you need interception for another reason, I hope this post is helpful. If you are looking through the DomainAspects 2.0 library code, I hope this will help you understand how and why I’ve used Windsor’s IoC container and their interception facility.

DomainAspects 2.0: Aspect Oriented Library with Generic WCF Host

Would you like to just focus on your business domain and host it anywhere easily rather than fussing with infrastructure? With DomainAspects you just write the interface in a project called [MyDomain].Common and the implementation is a project called [MyDomain]. Now add a brief and easy configuration string to both client and service host and xcopy deploy. Restart your hosting service if you are using the domain remotely.

With DomainAspects, all access to the implementation is now controlled as you have specified through easy attributes on your implementation without ever writing specific code to validate and authorize. Each and every call is logged without adding any logging code. Each and every exception is logged without ever adding a try/catch block in your code. And exceptions are captured, logged, and thrown up the chain to the client.

Introducing the 2.0 version of DomainAspects. This new version contains some major changes, the chief among them is the introduction of a WCF host container that allows you to access your aspect oriented domain layer remotely. More on the changes and features later, but first, let’s review what has come before and contributed to this new release:

And some earlier posts that have formed the creation of the WCF host in this new version:

DomainAspects 2.0 Introduced Today

Over the last couple of months, I’ve spend considerable time on the weekends to complete the idea of merging my previous work on a generic WCF host into my DomainAspects project. Since I had not touched either of these projects for some time, it was fun and interesting to look at them from a new perspective. There are many changes for the better here. Please download it and give it a try. I am eager to hear your feedback.

New in DomainAspects 2.0

Today, I’m releasing DomainAspects 2.0 which supports the following new features:

  • A WCF host service for accessing domain services running in another process or server with the following features/benefits:
    • Fast and encrypted net.tcp binding with Windows permissions.
    • Simplified custom configuration section that is the same on client and server.
    • Custom instance provider uses the DomainAspects service factory and Windsor for instance creation and release.
    • Ability to specify service instance lifestyle closely matching Windsor (PerThread, Pooled, Singelton, SingleCall).
    • Service host that will run in console mode for easy debugging.
    • Flip a config switch and install the service host as a windows service. No recompile required.
    • RemoteClient implements IDisposable correctly, dealing with Abort vs Close for you.
    • Host can be configured to use a fake PrincipalProvider for easily testing a variety of user roles and combinations.
    • Reliance on DomainAspects for authorization rather than configured local Windows groups.
  • A light remote client assembly allows access DomainAspects with minimal deployment requirements.
  • Local client and remote client both return an instance of the service interface, allowing a domain to be moved to another process or remote server with minimal changes to client code.
  • Upgrade to the current stable release Castle Windsor 3.0 container libraries.
  • Improved domain service factory allows greater instance creation control.
  • Additional tests, including multi-threaded remote tests focusing on instance creation and pooling.
  • More intuitive, self-describing classes to make understanding the code easier.

Using DomainAspects 2.0

Using the DomainAspects solution is easy. You must have Visual Studio 2010 Pro or higher. If you do not, you may spend considerable time trying to get it running. I would not recommend it.

Download the code and make sure you right click the zip file and go to properties and unlock the zip file before you extract it.

To run remote tests, be sure to start the DomainAspects.Host without debugging first. I recommend running the MultiRemoteTests class using a multiple project startup with the host running first and the DomainAspects.TestConsole running the test rather than the automated tests runner since I have not found a way to get those tests to run properly within the test harness.

Now run the tests. Watch the host console as the MultiRemoteTests run. Note the instance creation on second and third run take zero time because these instances have been pooled.

When you are ready to build your own domain service, just follow the examples in the TestDomain and TestDomain.Common projects. Configure the host and the client as shown in the app.config files in the included projects appropriately to match your new domain and domain.Common assemblies.

If you are going remote, build and deploy the DomainAspects.Host. Make sure you switch the “consoleMode” attribute in the config on the server side to “Off” so that it will install and run as a service.

Download DomainAspects20.zip (2.10 mb)