[Below is the summary of a lunch an learn session I did a few years back about AOP]

There are many paradigms in software development that tackle certain problems. OOP (Object Oriented Programming) along with design patterns help us build well-structure and loosely coupled solutions. OOP design patterns focus on functionalities.

Let's see an example in software development:

 

Aspect Oriented Programming or AOP focuses on processes rather than functionalities. It helps us with cross-cutting concerns such as caching, logging and security. In addition, AOP directs you to mainatin single responsibility and write modular, testable and reusable. 

There are a few definitions to consider.

  • Aspect: Information about where, when and what is to be done
  • Advice: The job of an aspect. Usually defines what and when of an aspect.
  • Pointcut: Defines where of an aspect.
  • Jointpoint: The point of execution where an aspect can be plugged in.
  • Target: The object which is being advised
  • Proxy: The created object after the advice has been applied
  • Weaving: The process of applying aspects

public class CustomerService:ICustomerService
{
private readonly ICustomerDb customerDb;
public CustomerService(ICustomerDb customerDb){
this.customerDb = customerDb;
}
//some other methods
public Customer GetCustomer(int id){
	return customerDb.GetCustomer(id);
}
}

Now if you need to cache the database calls, you can do it like below:

public class CustomerService:ICustomerService
{
private readonly ICustomerDb customerDb;
private readonly ICacheHandler cache; public CustomerService(ICustomerDb customerDb, ICacheHandler cache){ this.customerDb = customerDb;
this.cache = cache; } //some other methods public Customer GetCustomer(int id){
var cacheItem = cache.Get("CUSTOMER_" + id);
if(cacheItem != null){
return cacheItem as Customer;
}
var customer = customerDb.GetCustomer(id);
cache.Set("CUSTOMER_" + id,customer,TimeSpan.FromMinutes(60));
return customer; } }


The problem with the above implementation is that caching is not a functional requirement and shouldn't be mixed with functionality of the service. Also this causes issues with unit testing as we'll need to mock caching process for every method that is required to be cached.

To avoid this problem we should use AOP for caching and any other cross-cutting non-functional requirement such as logging and security.

Image from: https://i1.wp.com/www.karthikscorner.com/wp-content/gallery/design-patterns/AOP.png?w=676

Concepts in AOP

Aspect

  • A feature linked to different parts of the solution
  • Not related to the core functionality of the solution
  • Crosscuts the core concerns of the solution
  • Violates Separation of Concerns principle

Examples: Logging, transaction management, authorisation, auditing

 

Advice/Interceptor

  • The job of an aspect. Usually defines what and when of an aspect.

e.g. CachingInterceptor, CachingAdvice, LoggingInterceptor, DontAllowEditorsDeletePagesInterceptor

 

Pointcut

  • Defines where the aspect should be applied. It normally specifies a type of interface or a set of methods across the solution.
  • e.g. All the methods of class B that have a specific attribute

Jointpoint

  • The point of execution where an aspect can be plugged in.
  • e.g. before or after execution of the method.

Target

  • The object which is being advised/intercepted.

Proxy

  • The created object after the advice (interceptor) has been applied

Weaving

  • The process of applying aspects
  • The product of weaving is Proxy

The process of applying aspects

  • Different types of Weaving
    • Compile Time Weaving : Aspects are weaved into the class code when they are compiled.
    • Load Time Weaving : Aspects are weaved into the class code when the class is loaded (constructed) for the first time.

The steps

  • Creating an Interceptor/Advice
  • Configuring the Advice for Pointcuts or writing the required code to apply the interceptor to the right method(s)
  • Applying the Advice/Interceptor for specific Targets which is configuration of the DI framework.

Below is an example of an interceptor 

 


    public class LogExceptionAndReturnInterceptor : IInterceptor
    {
        private readonly ILogger logger;

        public LogExceptionAndReturnInterceptor(ILogger logger)
        {
            this.logger = logger;
        }

        public void Intercept(IInvocation invocation)
        {
            try
            {
                invocation.Proceed();
            }
            catch (Exception ex)
            {
                if (!invocation.Method.GetCustomAttributes(typeof(LogExceptionAndReturnAttribute), true).Any())
                {
                    throw;
                }

                logger.LogError(ex);

                object returnValue = null;

                if (invocation.Method.ReturnType == typeof(string))
                {
                    returnValue = "";
                }
                else if (invocation.Method.ReturnType.IsArray)
                {
                    returnValue = Array.CreateInstance(invocation.Method.ReturnType.GetElementType(), 0);
                }
                else
                {
                    returnValue = Activator.CreateInstance(invocation.Method.ReturnType);
                }

                invocation.ReturnValue = returnValue;
            }

        }
    }

What we are doing is basically, intercepting the calls and logging the exception and return the default value of the method return type. You might just throw the exception again but as you can see you have good control over the method invocation.