SheepAOP Part 3 – Aspect Lifecycles

This Series

  1. Getting Started with SheepAOP
  2. Pointcuts and SAQL Basics
  3. Aspects Lifecycles & Instantiations
  4. Integrating with IoC containers
  5. Aspects Inheritance & Polymorphism
  6. Attributive Aspects (ala PostSharp)
  7. Unit-testing your aspects
  8. Extending SheepAop

This Post

In this post, I will introduce you with the notion of aspect-lifecycle in SheepAop, a feature that I have claimed more than once to be among the utmost importance in an AOP framework for taking AOP’s real-world use beyond the highly clichéd logging and transaction toy-demo. Aspect-lifecycles offer some elegant answers to a whole array of real-world problems that are not normally solvable using conventional AOP solutions.

This post will take you through a generous amount of those examples to demonstrate some real-world applications of AOP in general, and of aspect-lifecycles in particular. No more Logging, Transaction, or SecurityAuthorization demo, I believe we’ve had enough of that 🙂

Overview

By default, SheepAop uses Singleton lifecycle, which means that only one instance of an aspect type exists. This is the simplest form of aspect instance. The state of a Singleton aspect is effectively global. Usually this arrangement is well suited for stateless aspects, as well as aspects with an inherently global state, such as caching or resource-pool.

But in some situations, especially when you are creating reusable aspects, you want to associate the aspect’s state in a very specific manner (e.g. per individual object, or a particular class, or per scope of a control flow).
The aspect-lifecycle mechanism in SheepAop offers various ways to control the lifecycles of aspect instances, and offers many interesting and powerful design choices.

There are several kinds of aspect lifecycles in SheepAop:

  • Singleton (default)
  • Transient
  • Per object (PerThis and PerTarget*)
  • Per control-flow
  • Per type*

*) Not yet supported

Note: It’s very trivial to implement your own aspect-lifecycle, e.g. WebRequestLifecycle, or WebSessionLifecycle. We’ll cover that on “Extending SheepAop” later in the series.

Singleton Lifecycle (Default)

Singleton-lifeycle is in effect when you don’t specify any lifecycle in your aspect class declaration.

[Aspect]
public class MyAspect
{
}

You can also declare it explicitly using SingletonAspect, which makes no difference whatsoever.

[SingletonAspect]
public class MyAspect
{
}

Let’s write a simple toy program that creates 2 Account objects and calls their methods.

public class MyProgram
{
   public static void Main()
   {
      var account1 = new SavingAccount(12345);
      var account2 = new SavingAccount(77777);

      account1.Credit(200);
      account1.Debit(100);

      account2.Credit(200);
      account2.Debit(100);
   }
}

And apply some aspect:

[Aspect]
public class MyLoggingAspect
{
   var _logger = logger;
   public MyLoggingAspect(ILogger logger)
   {
      _logger = logger;
      _logger.Trace("> Creating MySingletonAspect instance");
   }

   [SelectMethods("'void SavingAccount::*(System.Int32)'")]
   private void AccountPointcut();

   [Around("AccountPointcut")]
   pulbic void AroundAccountMethods(MethodJoinpoint jp)
   {
      _logger.Trace(">> Calling {0}({1}) on {2}", jp.Method, jp.Args[0], jp.This);
      jp.Execute();
   }
}

PS: The ILogger parameter on the constructor is injected by your IoC container. We’ll look into how to hook SheepAop with your IoC container in the next post or two.

The output is:

> Creating MyLoggingAspect instance
>> Calling Credit(200) on SavingAccoung{12345}
>> Calling Debit(100) on SavingAccoung{12345}
>> Calling Credit(200) on SavingAccoung{77777}
>> Calling Debit(100) on SavingAccoung{77777}

Only one MySingleAspect instance will ever be created over the course of the program, which in this case also means that you will always append all log entries to only one ILogger instance.
(All previous examples in the series so far use Singleton lifecycle by default, so I won’t bother you with another example on Singleton aspects).

Transient Lifecycle

Transient lifecycle will create a new aspect instance for every time a join-point is hit.
Let’s use the same toy program from the example above, and just slightly change the aspect declaration to the following:

[TransientAspect]
public class MyLoggingAspect
{
   /* unchanged */
}

Now the output will become like the following:

> Creating MyLoggingAspect instance
>> Calling Credit(200) on SavingAccoung{12345}
> Creating MyLoggingAspect instance
>> Calling Debit(100) on SavingAccoung{12345}
> Creating MyLoggingAspect instance
>> Calling Credit(200) on SavingAccoung{77777}
> Creating MyLoggingAspect instance
>> Calling Debit(100) on SavingAccoung{77777}

A new MyTransientAspect instance is created for every join-point hit, which will append a log entry to a potentially different ILogger instance each time.
There really are not many realistic scenarios where Transient lifecycle would be necessary, but it can be useful especially when you want to make use of the lifecycle-capability provided by your IoC container. More about IoC integration in the next post.

Per Object Lifecycles (PerThis and PerTarget)

If an aspect A is defined AspectPerThis(Pointcut), then one object of type A is created for every instance that is the executing object (i.e., “This”) at any of the join points picked out by the Pointcut.
The advice defined in aspect A will run only at a join point where the currently executing object has been associated with an instance of A.

So if we modify our first toy (logging-aspect) example as follows:

[PerThis("AccountPointcut")]
public class MyLoggingAspect
{
   /* unchanged */
}

.. the output will become:

> Creating MyLoggingAspect instance
>> Calling Credit(200) on SavingAccoung{12345}
>> Calling Debit(100) on SavingAccoung{12345}
> Creating MyLoggingAspect instance
>> Calling Credit(200) on SavingAccoung{77777}
>> Calling Debit(100) on SavingAccoung{77777}

Everytime the “AccountPointcut” hits a join point, SheepAop will look at the object bound to This (the current SavingAccount instance), and create a new aspect instance if there is not already a MyLoggingAspect in existence for that SavingAccount instance. The aspect instance is eligible for garbage collection at the same time as the object it is associated with.

The PerTarget model works in a very similar manner to PerThis. If an aspect A is defined AspectPerTarget(Pointcut), then one object of type A is created for every object that is the Target object of the join points picked out by Pointcut. The advice defined in A will run only at a join point where the target object has been associated with an instance of A.
Note: Target object is only applicable to join-points matched using call-pointcuts, not member-pointcuts. Therefore consequently only call-pointcuts can be used with PerTarget lifecycle.

Example: Concurrent Object

In this example, we will create an aspect to convert your non-thread-safe class into a thread-safe one by applying the read-write lock pattern, hence allowing concurrent access to the objects. This pattern requires one lock for each object instance. The per-object lifecycles provide a mechanism to associate a new aspect isntance with each execution (‘This’) or ‘Target’ object.

In the following example, we will use AspectPerThis lifecycle to associate each of your ShoppingCart objects with a new ReadWriteLockAspect.

[AspectPerThis("Readings", "Writings")]
public class ReadWriteLockAspect
{
   [SelectMethod("'* ShoppingCart::Get*(*)'")]
   [SelectPropertyGet("'* ShoppingCart::*'")]
   protected void Readings(){}

   [SelectMethod("'void ShoppingCart::*(*)'")]
   [SelectPropertySet("'void ShoppingCart::*(*)'")]
   protected void Writings(){}

   private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

   [Around("Readings")]
   public object LockRead(JoinPoint jp)
   {
      try
      {
         _lock.EnterReadLock();
         return jp.Execute();
      }
      finally()
      {
         _lock.ExitReadLock();
      }
   }

   [Around("Writings")]
   public void LockWrite(JoinPoint jp)
   {
      try
      {
         _lock.EnterWriteLock();
         jp.Execute();
      }
      finally()
      {
         _lock.ExitWriteLock();
      }
   }
}

Note: Ideally, given that the read/write lock pattern itself is reusable, you would define this aspect as abstract (with abstract pointcuts) so you can reuse the aspect. We will cover this in part 5 (“Aspect Inheritence”).

So now each of your ShoppingCart instance will be assigned with a ReadWriteLockAspect, which will lock and release read/write access to your object. We have just converted our ShoppingCart to be thread-safe without introducing any additional noise into your actual code.

Examples: Timing-Out DbConnection Instances

In this example, we will write an aspect to limit the duration for which each DbConnection instance may keep its connection open. We’ll set a timer to forecfully close DbConnection instances that have been left open for over a certain period of time. Since the timer is associated with each connection object, we’ll need an aspect with per-object lifecycle (in this case, PerTarget).

[AspectPerTarget("OpeningConnection")]
public class ConnectionTimeoutAspect
{
   [SelectCallMethods("'void System.Data.DbConnection::Open()'")]
   protected void OpeningConnection(){}

   [SelectCallMethods("'void System.Data.DbConnection::Close()'")]
   protected void ClosingConnection(){}

   private const int TIMEOUT = 1000;
   private Timer timer;

   [Around("OpeningConnection")]
   public void AroundOpeningConnection(JoinPoint jp)
   {
      timer = new Timer(_=> ((DbConnection)jp.Target).Close(), null, 0, TIMEOUT);
   }

   [Around("ClosingConnection")]
   public void AroundClosingConnection(JoinPoint jp)
   {
      timer.Dispose();
   }
}

Example: Actor Model

Ayende recently blogged about his Actor implementation. In this example, we’ll look at how to reimplement the solution using aspect-oriented approach. The idea is to enable actor capability on an arbitrary POCO object without modifying its codes.
The premise of an actor is that all non-read actions must be executed asynchronously (non-blocking), but these actions must be executed sequentially within each actor. Consider the following actions from Ayende’s example:

connection1.Send("abc");
connection1.Send("def");

I care for “abc” to be sent before “def”, and for both to be sent before anything else is sent through connection1. But I do NOT care if I have anything else sent between “abc” and “def” (through different connections).
Additionally, every read operation must be executed synchronously, while still maintaining the order of executions. (i.e. all read operations should execute after all previous queued actions have completed).

Since each actor object requires its own sequential tasks-queue, we need to associate an aspect with each actor instance. So let’s create an aspect with PerThis lifecycle to target our (synchronous) Connection class.

[AspectPerThis("NonReadActions")]
public class ActorAspect
{
   [SelectTypes("'Sheep.MyConnection'"]
   protected void TargetClass(){}

   [SelectMethods("Type:@TargetClass & !ReturnsVoid"]
   protected void ReadOperations(){}

   [SelectMethods("Type:@TargetClass & !@ReadOperations"]
   protected void NonReadActions(){}

   private readonly ConcurrentTaskQueue _queue = new ConcurrentTaskQueue();

   [Around("NonReadActions")]
   public void AroundActions(JoinPoint jp)
   {
      _queue.EnqueueToExecution(()=> jp.Execute());
   }

   [Around("ReadOperations")]
   public object AroundReading(JoinPoint jp)
   {
     using(_queue.EnterLock())
     {
         _queue.WaitTillCompleted();
         return jp.Execute();
     }
   }
}

Note: Similar to the previous example, this aspect should ideally be defined as an abstract aspect so we can reuse the actor pattern whenever it’s needed.

All the synchronous methods in our Connection class have now been fully converted to work synchronously, and follow the actor-model behavior that we desire. It’s all done without cluttering the actual Connection class with the complex plumbing of Actor pattern. We extract the pattern out into an aspect, leaving your original class clean from irrelevant bits of technical complexities.

Other Examples:

There are many other real world applications of PerThis and PerTarget lifecycles, including:

  • Dirty-tracking (tracks all changes of fields and properties within an object)
  • Lazy-Load proxy
  • Cache the output of a costly method that is called multiple times on the same object, e.g. fileStream.ReadToEnd()

Per-Control-Flow Lifecycle

If an aspect A is defined AspectPerFlow(Pointcut), then one object of type A is created for each flow of control of the join points picked out by Pointcut as the flow of control is entered. The advice defined in A may run ONLY at any join point within that control flow. An instance of the aspect is created upon entry into each such control flow.

Example: Context Passing

Consider you are developing a flight-booking application. As part of the booking process, the application will create flight itineraries objects.

var itinerary = new Itinerary(flight);

The constructor also automatically sets the CreatedDateTime property of the Itinerary object with the current system time.

Today you are requested to allow the system to be used by a travel-agent company who bulk their booking transactions in an end-of-day basis. So we provide them with the following client-facing API:

public void BookFlightFromTime(DateTime orderTime, Flight flight, Customer customer /* etc etc*/)
{
}

Note the first parameter “orderTime“. Being an end-of-day bulk operation, all Itinerary objects created within the scope of this method must have their CreatedDateTime properties assigned with the specified orderTime, rather than the current (end-of-day) system time.
Typically this requires passing the orderTime from the client, down through every method call that eventually leads to Itinerary creations. All programmers are familiar with the inconvenience of adding a method argument to a number of methods just to pass this kind of context information.

Using SheepAop, this kind of context passing can be implemented in a modular way. The following code adds an Around advice that runs when an Itinerary object is instantiated within the control-flow of BookFlightFromTime method.

[AspectPerFlow("BookFlightFromTimeMethod")]
public class OrderDateAspect
{
   [SelectMethod("void BookingClient::BookFlightFromTime(DateTime, *)")]
   protected void BookFlightFromTimeMethod(){}

   [SelectConstructor("InType: 'Sheep.Itinerary'")]
   protected void CreatingItenerary(){}

   private DateTime _orderTime;

   [Around("BookFlightFromTimeMethod")]
   public void BookFlightFromTimeMethod(MethodJoinPoint jp)
   {
      _orderTime = jp.Args[0];
   }

   [Around("CreatingItenerary")]
   public void AroundCreatingIntenerary(JoinPoint jp)
   {
      jp.CreatedDateTime = _orderTime;
   }
}

This aspect affects only a small number of methods, but note that the non-AOP implementation of this functionality might require editting many more methods, specifically, all the methods in the control-flow from the client to the Itenerary creation. This is one major characteristic of aspect-lifecycle control. While the aspect is short and affects only a modest number of benefits, the complexity the aspect saves is potentially much larger.

Example: Transactional Methods: The Right Way

Transaction is one of the most cliche example in aspect-oriented-programming examples, and I’ve promised I would not mention the word today. But this one is not your typical transaction AOP demo.
The typical implementations in most AOP demos are usually overly naive. To explain what I mean, let’s write a simple ordinary TransactionAspect (using default Singleton lifecycle)

[Aspect]
public class TransactionAspect
{
   [SelectMethods("'* Sheep.ApplicationService.*::*(*)' & Public")]
   private void TransactionalMethods(){}

   [Around("TransactionalMethods")]
   public object AroundTransactionalMethods(JoinPoint jp)
   {
      using(var tx = new Transaction())
      {
         var returnValue = jp.Execute();
         tx.Commit();
         return returnValue;
      }
   }
}

That was quite straightforward. But now consider if you have non-transactional operations within your methods, e.g. sending an email. You don’t want to send out the email if the transaction fails. Furthermore, if you apply automatic retries on your transaction aspect, you won’t want multiple duplicate emails sent out.
It’s usually hard to solve this problem using other AOP solutions that do not support any notion of lifecycle control.

We will modify our TransactionAspect above to intercept all calls to non-transactional methods, and defer their executions until at the end of the transaction (upon successful completion of the transaction).

[AspectPerFlow("TransactionalMethods")]
public class TransactionAspect
{
   [SelectMethods("'* Sheep.ApplicationService.*::*(*)' & Public")]
   private void TransactionalMethods(){}

   [SelectMethods("HasCustomAttributeType: 'Sheep.NonTransactionalAttribute' & ReturnsVoid")]
   private void NonTransactionalMethods(){}

   private Queue<Action> _nonTransactionalActions = new Queue<Action>();

   [Around("NonTransactionalMethods")]
   public void StealNonTransactionalMethods(JoinPoint jp)
   {
      _nonTransactionalActions.Add(()=> jp.Execute());
   }

   [Around("TransactionalMethods")]
   public object AroundTransactionalMethods(JoinPoint jp)
   {
      using(var tx = new Transaction())
      {
         var returnValue = jp.Execute();
         tx.Commit();

        foreach(var action in _nonTransactionalActions)
           action();
        return returnValue;
     }
   }
}

Now all executions of non-transactional methods will be intercepted, and stored to a queue to be executed later when the transaction is completed.
A non-AOP implementation of this solution is non-trivial. You would have to massively change the structure of your code to pull out all calls to non-transactional methods, move them to the end of the transaction, and somehow maintain the contexts that are required by those methods.

Summary

Aspect-lifecycle is a key feature in SheepAop that takes aspect-orientation from logging and transaction to a whole another class of far more complex real-world problems.
The ability to associate the state of an aspect to a specific program context allows rich yet elegant aspect-oriented solutions, often affecting only a modest number of areas, but would otherwise require potentally a significantly larger complexity, if possible at all, using conventional (singleton) AOP solutions.
Later in the series, we will explore how you can create your own aspect-lifecycle implementations, such as PerHttpSessionLifecycle, or PerHttpRequestLifecycle

Advertisements

One thought on “SheepAOP Part 3 – Aspect Lifecycles

  1. thanks for your great work

    there is no sample about “PerHttpRequestLifecycle” and “PerHttpSessionLifecycle”

    where do we can pull a request for your project?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s