AOP vs decorator

Posted by mgroves on March 28, 2012  •  Comments (2)  •  Tags: patterns, decorator, DynamicProxy

I came across this question about interception vs injection on Programmer's Stack Exchange. Since I'm always blogging and talking about AOP, my thoughts immediately went to suggesting an AOP framework.

However, Mark Seeman wrote a very good answer about using the decorator pattern (or proxy pattern depending on how you look at it), and beyond being a good answer, it made me pause and think about the implications. If decorator/proxy combined with dependency injection does the job, why would I ever introduce an AOP tool?

Well, in many cases the answer is: I shouldn't! If I'm only applying cross-cutting concerns to a handful of classes, it's totally feasible for me to write out proxy/decorators and wire them up in my IoC container. Consider:

  • 2 service interfaces: ICustomerRepository and IProductRepository
  • 2 implementations: CustomerRepository and ProductRepository (which implement the interfaces respectively)
  • 2 logging decorators: CustomerLoggingDecorator and ProductLoggingDecorator (which also implement the interfaces respectively)
  • 2 caching decorators: CustomerCachingDecorator and ProductCachingDecorator (which also implement the interfaces respectively)

And there you go. No post-compile step, no runtime reflection generation, just you, 8 classes/interfaces and your IoC tool of choice. The logging and caching decorator classes could be very similar in their implementations, but it's not so bad, and I still get a nice separation of concerns, and all my implementations are single responsibility.

But now consider:

  • 100 service interfaces: ICustomerRepository, IProductRepository, and 98 others...
  • 100 service implementations: CustomerRepository, ProductRepository, and 98 others...
  • 100 logging decorators: CustomerLoggingDecorator, ProductLoggingDecorator, and 98 others...
  • 100 caching decorators: CustomerCachingDecorator, ProductCachingDecorator, and 98 others...

Whoa. Well, I still have a very nice separation of concerns, and I'm still following single responsibility. Wiring them up in a good IoC tool shouldn't be a problem, so long as I follow a good naming convention. So this isn't terrible.

But, now I have 400 classes/interfaces, most of which probably have very similar boilerplate code throughout. If I make a change to that boilerplate in one class, I have to change it in (possibly) 99 other classes. I still think this architecture is better than just tangling logging/caching code inside of the service implementations, it still leaves a lot to be desired.

Now consider an architecture using, say, Castle DynamicProxy:

  • 100 service interfaces: ICustomerRepository, IProductRepository, and 98 others...
  • 100 service implementations: CustomerRepository, ProductRepository, and 98 others...
  • 1 logging interceptor class: LoggingInterceptor
  • 1 caching interceptor class: CachingInterceptor

I think this is much better. No repetition with logging and caching decorators. I can still easily wire up the dependencies with a good IoC tool. If my logging or caching needs to change, I only need to change one place. If I actually need different logging or caching implementations, I can still do that. The only major difference is that now I have to use an AOP tool like DynamicProxy or PostSharp.

So, when should you use the decorator/proxy patterns and when should you use an AOP tool? With 2 service classes, you can definitely get by with your own decorators. With 100 service classes...maybe not so much. "Somewhere in between" is my weaselly answer.

  • If you know your app will have a ton of service classes, you may as well start with an AOP tool.
  • If you know your app will only have a small amount, you may as well stick to decorator/proxy.
  • If you aren't sure, start with decorator/proxy and move to an AOP tool when you feel like there's too much boilerplate and repetition.

If you plan your architecture carefully, you can switch back and forth between tool and tool-less with a minimum of effort.

Posted by Steven
on March 30, 2013

You are forgetting one very important thing here. In your achitecture, you seem to have many abstractions that are conceptually very strongly related, but seem to miss a technical relationship. There is an common abstraction missing from your architecture.

In your case the ICustomerRepository and IProductRepository interfaces should be replaced with a single IRepository<T>. You will get an implementation for IRepository<Customer> and one for IRepository<Product>. By doing this, you only have to create two decorators: LoggingRepositoryDecorator<T> and CachingRepositoryDecorator<T>.

Although it is common to add many query methods to custom repositories such as an GetAllCustomersByFirstNameLike() method on ICustomerRepository, this actually violates the Interface Segregation Principle and will result in a customer repository implementation that breaks the Single Responsibility Principle.

In that case again an abstraction is missing: an abstraction over queries. When giving each query method its own class (again based on a generic interface), the repository interface stays clean and can easily be decorated AND any executed query can be decorated easily, without having to fall back to using interception or code weaving.

Posted by Julie Ingle
on May 10, 2013

Matthew you gave an awesome AOP presentation Thursday night. I would have said so in person, but I did not want to wait in the queue. I especially enjoyed the examples of what tangled and scattered looked like and the progressive why of improving the code without using AOP. It helped me to appreciate AOP so much more seeing that. Kudos to you for your excellent teaching skills! I was not aware of this topic. Now I know what it is and have a book to reference. Thanks.

Leave a Comment

* = Required
*
*

Some HTML will be escaped, you can use StackOverflow-flavored Markdown