Cross cutting concerns (wiki) are classes which interact with most classes in the application that you are developing. Logging, Instrumentation, Caching, Transactions are common examples.

Case 1: Class does not know about the concern

If the class should not know about the concern, use decorator pattern or AOP.

Decorator pattern

Decorator pattern is an elegant solution, if properly implemented, to implement concerns.

One decorator for each interface

Below is an interface for which instrumentation concern is added:

Instrumentation can be added as a decorator with the following class:

Here is how the class implementing IDemo interface can be used:

Problem with too many interfaces

The main problem with using the decorator pattern is when the number of interfaces to be decorated is large. If there is another interface IDemo2 which needs to be decorated, the following is the code:

IDemo2:

Decorator:

As you can see from the above example, InstrumentationDecorator decorates all the interface – IDemo, IDemo2. As the number of interface increases, the decorator class becomes large with repetitive code.

Decorator as DynamicObject

The problem of writing repetitive code can be offset by emitting IL code to generate decorator interface types. DuckTyping is an open-source library for generating dynamic proxy types. For implementing the instrumentation decorator, IL code can be emitted as shown in the DuckProxyType class. If the dependency injection framework has support for dynamic types, the following decorator will work well.

The decorator can be used using the dynamic keyword:

Aspect oriented programming

Caching

The decorator pattern explained above decorates every method in the interface. For some concerns like Caching, not all method needs to be decorated. For example, consider the following interface:

For the above interface, the object retrieved from the Get methods have to be cached. The Create, Update, Delete method should not be decorated with a Caching decorator. This problem can be offset by using attributes for the Get methods. But, there is an elegant solution to this problem: Aspect oriented programming or AOP.

Why AOP is better

The Caching decorator looks something like the below:

Using decorator pattern for caching is a bad choice because of the following reasons:

  • The number of interfaces that require caching support are few.
  • Only a few methods within each interface require caching support.
  • Adding attributes to the class makes the class aware of the concern.

AOP is another way to implement concerns. AOP requires framework support like PostSharp. In this post, I will provide a basic AOP framework for handling the caching problem.

Simple AOP framework

At the heart of AOP is the interceptor object. Every method call that requires AOP support will be intercepted. The interceptor knows the concern that should wrap around the method call. AOP has terminology for each of the above concepts:

  • PointCut – Method to intercept.
  • Advice – Type that implements the concern.
  • JoinPoint – Information about the interception.

Consider an interface, IService, which has a method whose output has to be cached.

PointCut is nothing but the MethodInfo of the GetData method. PointCut can be implemented as below:

PointCut is stored in a dictionary. Hence, it implements the IEqualityComparer interface.

IAdvice interface implements the concern. It has a single method which has information about the inner invocation encapsulated in the form of a JoinPoint object.

Any AOP framework has two additional classes:

  • Registry – Dictionary which has the mapping between PointCut and Advice
  • Interceptor – Dynamic object which intercepts the interface method call.

AOP Registry:

AOP Interceptor:

AOP Interceptor checks whether the registry has an advice for the method call. If there is an advice, the advice method is called. If there is no advice, the invocation proceeds as normal.

To see how all of this works, we can implement a CachingAdvice which intercepts the call to IService – GetData method.

Finally, here is the plumbing code that initializes the AOP framework and does the method call.

The above AOP framework is simplistic. If the number of PointCuts for a concern (advice) is quite small, then the AOP framework is useful. This is the case for caching.

Case 2: Class knows about the concern

If the class should know about the concern, using Dependency injection is the best option. Logging is a concern which some classes should know about. Before explaining why some classes should know about logging, I will explain how AOP frameworks handle logging.

Logging

Most methods log traces at the beginning of the method and at the end of the method. If an exception occurs, then the method logs the exception at the error level. If the method succeeds, then the method logs an informational message. The following code shows a typical method with logging code.

Logging with PostSharp

The above code reduces a single line of code with PostSharp logging. With PostSharp, the above method will reduce to a single line of code.

PostSharp MethodBoundaryAspect hooks onto 4 events within a method:

  1. OnEntry
  2. OnExit
  3. OnSuccess
  4. OnException

These events are interweaved into the IL code by PostSharp during the build process. LoggingAspect will do the actual logging.

Problem with AOP for Logging

While the above code may appeal to most naive developers as a good enough reason to use AOP framework, a standard logger decorator can do a similar job.

The real problem with logging

The reason why logging is the most revered cross-cutting concern (or aspect) is because of the need to trace within methods. Consider the following method:

To be able to trace messages within the method body, the method needs to be aware of the logger. The logger object can be injected before the method call using a property setter.

Dependency injection and Decorator combined

The class containing the GetResults method can have a Logger property as follows:

The Logger property can be injected using the standard decorator class.

Before calling the inner object method, we check if there is a Logger property. If there is a Logger property, we can inject the Logger object using that property. This makes the logger available for tracing.

Summary

There are three approaches to implementing cross-cutting concerns:

  1. Decorator pattern.
  2. Aspect oriented programming and Interceptor pattern.
  3. Dependency injection along with Decorator pattern.

To implement cross-cutting concerns, a combination of all three approaches should be used. The architecture of the application should specify the method used to implement a concern:

  • Logging – DI
  • Caching – AOP
  • Instrumentation – Decorator

The chosen DI container should have good support for dynamic types and interception.

Guide to implement cross cutting concerns
Tagged on:

Leave a Reply

Your email address will not be published. Required fields are marked *