Aspect-Oriented Programming (AOP)


By Aleksandar Seovi

In the last couple of years, AOP has been generating a lot of buzz in the software development community. AOP was invented in the late 1990s by Gregor Kiczales [Gregor Kiczales] and his team at Xerox PARC labs in an attempt to solve a problem of code duplication in object-oriented systems by encapsulating crosscutting concerns of the system into reusable aspects.

AspectJ [AspectJ], which could be considered a reference AOP implementation, was released in 2001 and has spurred a lot of activity in the Java community, including a number of alternative AOP implementations.

While most of the AOP-related activity is still happening in the Java community, there are several .NET AOP implementations that you can use today to reap the benefits of AOP, such as Spring.NET AOP [Spring.NET], Loom.NET [Loom.NET] and AspectSharp [AspectSharp].

What Is the Buzz About?

The basic premise behind AOP is that even though OOP helps reduce the amount of code duplication when compared to procedural languages, it still leaves a lot to be desired. Core OOP features, such as inheritance and polymorphism, as well as documented design patterns, such as Template Method [GoF Design Patterns], help to minimize duplication in the core application logic. However, code that implements crosscutting concerns, such as logging or security, is still very difficult if not impossible to modularize using OOP by itself.

Even though every article ever written on AOP that you run across will probably have the same boring example, we will use method invocation logging to show you what types of problems AOP tries to address.

Let's assume that we've implemented a simple calculator service that supports four basic arithmetic operationsaddition, subtraction, division and multiplicationas the following code shows. Note how simple and readable the methods are because they only contain the core logic.

public class Calculator : ICalculator {     public int Add(int n1, int n2)     {         return n1 + n2;     }     public int Subtract(int n1, int n2)     {         return n1 - n2;     }        public int Divide(int n1, int n2)     {         return n1 / n2;     }     public int Multiply(int n1, int n2)     {         return n1 * n2;     } }


For the sake of argument, let's say that a new user request comes up that requires you to log every call to your service methods. Class name, method name, parameter values passed to it, and its return value should be logged.

Using only OOP, this seemingly simple request would force you to modify all four methods and add logging code to them. The end result would look similar to this:

using System; namespace LoggingAspectDemo {     public class LoggingCalculator : ICalculator     {         public int Add(int n1, int n2)         {             Console.Out.WriteLine("-> LoggingCalculator.Add                 (" + n1 + ", " + n2 + ")");             int result = n1 + n2;             Console.Out.WriteLine("<- LoggingCalculator.Add                 (" + n1 + ", " + n2+ ") returned " + result);             return result;         }               public int Subtract(int n1, int n2)         {            Console.Out.WriteLine("-> LoggingCalculator.Subtract                (" + n1 + ", " + n2 + ")");            int result = n1 - n2;            Console.Out.WriteLine("<- LoggingCalculator.Subtract                (" + n1 + ", " + n2 + ") returned " + result);            return result;         }              public int Divide(int n1, int n2)         {             Console.Out.WriteLine("-> LoggingCalculator.Divide                 (" + n1 + ", " + n2 + ")");             int result = n1 / n2;             Console.Out.WriteLine("<- LoggingCalculator.Divide                 (" + n1 + ", " + n2 + ") returned " + result);             return result;         }         public int Multiply(int n1, int n2)         {             Console.Out.WriteLine("-> LoggingCalculator.Multiply                 (" + n1 + ", " + n2 + ")");             int result = n1 * n2;             Console.Out.WriteLine("<- LoggingCalculator.Multiply                 (" + n1 + ", " + n2 + ") returned " + result);             return result;         }     } }


As you can see, similar logging code exists in each of the methods.

Note

As you understand, the previous example could have been done a bit better even without AOP, such as by using the Decorator pattern [GoF Design Patterns], but we are trying to make a pedagogic point here, so please be patient.

It's also the case that the Decorator pattern would affect the consumer because it has to instantiate the decorator instead of the decorated class, and if you need to add multiple services you need to implement and chain multiple decorators.


There are many problems with this approach:

  • There is a lot of code duplication, which is never a good sign. In this case, duplicate code could possibly be simplified by creating static utility methods for logging that would take class and method name and parameter or return values as arguments, but duplication itself cannot be removed using OOP.

  • Logging code distracts a reader from the core logic of the method, thus making the method less readable and more difficult to maintain in the long run.

  • Code that implements core logic is longer than necessary. Methods that could've been simple one-liners had to be broken into result assignment and its return, just so the result could be printed in the logging code.

  • If you had to remove logging code at some point, you would be faced with a daunting task of manually removing or commenting out all the logging code from your classes.

  • If you decided that you want to log the full class name, including namespace, you would have to find all the methods that contain logging code and add missing namespace information by hand.

  • The same applies if you decide that you want to log exceptions such as overflow and division by zero when they occuryou'd have to add a try/ catch block and a statement that would log exceptions in many, many places throughout your code base.

Of course, one could argue that if you used a sophisticated logging solution, such as Log4Net [Log4Net] or Enterprise Library Logging Application Block [Enterprise Library Logging], instead of plain Console.Out.WriteLine statements, you could turn logging off by simply changing the configuration setting, without the need to touch any of your classes.

While that is entirely true, it really misses the point in this case. Your core logic would still be intermingled with ancillary logging code, and you would still have to edit many methods by hand if you wanted to make any changes to the logging logic. Moreover, if we change our example from logging to security policy enforcement or transactions, you won't be able to turn it on or off so easily either.

What AOP promises to do for you is to allow you to separate crosscutting code, such as logging or security, into aspects and to apply those aspects easily to all the classes and methods that need them. Of course, there is a lot more to AOP than this. It also allows you to separate business rules from the core logic and to change your existing objects by adding both state and behavior to them. We'll see examples of this in the following sections.

What is important to understand is that AOP is not a replacement for OOPit is simply an extension to it that allows you to go to places that are difficult to reach with OOP alone. Good object-oriented principles and practices still apply. Actually, it is probably even more important than ever before to have a solid OO design in place, as it will make application of aspects much easier.

AOP Terminology Defined

Before we dig deeper and show you examples of AOP in action, it is important to understand basic AOP terminology, which is not as difficult as it first seems. So I offer some definitions for the following terms:

  • An advice is a piece of code that you want to encapsulate and reuse. For example, logging code would be implemented as an advice and applied wherever you need it. There are several types of advice, such as before, after, and around advice.

  • An introduction is a somewhat special type of advice that only applies to classes. Introductions allow you to introduce new members into existing classes, both state and behavior, and could be used to achieve the benefits of multiple inheritance in languages that do not support it, without its tradeoffs. (Introduction is called mixin in some languages.)

  • A joinpoint is any place in the code where an advice could be applied. In theory, a joinpoint could be almost anythinginstance variable, for loop, if statement, and so on. In practice, however, most commonly used join-points are classes, methods, and properties.

  • A pointcut identifies a set of joinpoints where advice should be applied. For example, if you want to apply transaction advice to all the methods that are marked with transaction attribute, you would have to declare a pointcut that identifies those methods.

  • An aspect groups advices and the pointcuts they apply to, which is in a way similar to how a class groups data and associated behavior together.

AOP in .NET

Enough theorylet's see how AOP can help us solve real-world problems that we face almost every day during development.

Examples in this section are built using the Spring.NET AOP implementation, so you will need to download and install the latest Spring.NET release in order to try them.

Note

Please note that, as of this book's writing, the examples in this section are based on the planned implementation of the AOP support in Spring. NET. Visit this book's Web site (www.jnsk.se/adddp) for information about changes, or check the official Spring.NET documentation, which can be found at www.springframework.net.


Modularizing Logging Code Using an Aspect

So let's see how we can leverage AOP to solve the problems associated with the previous logging code.

Creating a Logging Advice

The first thing we need to do is create an advice that will encapsulate logging code:

using System; using System.Text; using AopAlliance.Intercept; namespace LoggingAspectDemo {     public class MethodInvocationLoggingAdvice         : IMethodInterceptor     {         public object Invoke(IMethodInvocation invocation)         {             Console.Out.WriteLine("-> " +                 GetMethodSignature(invocation));             object result = invocation.Proceed();             Console.Out.WriteLine("<- " +                 GetMethodSignature(invocation)                 + " returned " + result);             return result;        }        private string GetMethodSignature            (IMethodInvocation invocation)        {            StringBuilder sb = new StringBuilder();            sb.Append(invocation.Method.DeclaringType.Name)                .Append('.')                .Append(invocation.Method.Name)                .Append('(');            string separator = ", ";            for (int i = 0; i < invocation.Arguments.Length;                i++)            {                if (i == invocation.Arguments.Length - 1)                {                    separator = "";                }                sb.Append(invocation.Arguments[i])                    .Append(separator);           }           sb.Append(')');                  return sb.ToString();        }     } }


This advice will log the class name, method name, and parameter values and return values for each call to the method to which it is applied.

It uses the ToString() method to print parameter and return values. That might not be sufficient in all cases, but it should work for the vast majority of methods, especially if you take care to implement ToString() for your application classes. In the case of an exception, this advice will simply propagate it, so any error handling code you might have in place should work fine.

It's in the Invoke() method where most of the magic happens. What we are basically saying here is that we want to log the start of the method call, capture the result by assigning the value of the invocation.Proceed() call to a result variable, and log the result before returning it. The call to invocation.Proceed() is criticalthat is what causes our calculator method to be executed.

The GetMethodSignature() helper method is used to build a method signature in a generic fashion by inspecting the invocation object. You'll probably notice that we called GetMethodSignature() twice, even though it seems that we could've optimized the advice a bit by assigning its result to a local variable and using that variable instead. The reason for that is so that we can also see any changes in the parameters that were used, in case they are modified by our advised method. This is especially helpful when logging calls to methods that have "out" parameters.

Now that we have our advice defined, what happens if we find out that we need to log any exceptions that the advised methods throw? As you can probably guess by now, this change is trivialall we need to do is add necessary logic to our advice's Invoke() method:

public Object Invoke(IMethodInvocation invocation) {     Console.Out.WriteLine("-> "         + GetMethodSignature(invocation));     try     {         object result = invocation.Proceed();         Console.Out.WriteLine("<- "             + GetMethodSignature(invocation)             + " returned " + result);         return result;     }     catch (Exception e)     {         Console.Out.WriteLine("!! "             + GetMethodSignature(invocation)             + " failed: " + e.Message);         Console.Out.WriteLine(e.StackTrace);         throw e;     } }


Changing GetMethodSignature() to include the class namespace is even simpler, so we'll leave it as an exercise to the reader.

Applying a Logging Advice

Creating an advice is only half of the story. After all, what good is an advice if it's never used? We need to apply the logging advice to our Calculator class from the first code listing, which in Spring.NET can be accomplished in two ways.

The first approach is to apply it using code. While this is very simple in this particular case, it is not ideal because you are manually applying an advice to a particular object instead of relying on the container to apply it automatically for you wherever necessary based on aspect configuration. Nevertheless, let's see how we can apply our logging advice in the code.

using Spring.Aop.Framework; ... ProxyFactory pf = new ProxyFactory(new Calculator()); pf.AddAdvice(new MethodInvocationLoggingAdvice()); ICalculator calculator = (ICalculator) pf.GetProxy());


That's itwith only three lines of code you have applied the logging advice to all the methods of the Calculator instance and obtained a reference to it. You can now call any method on it, and you will see that the calls are properly logged.

The Spring.NET declarative approach to AOP is much more powerful and doesn't require you to write any code. However, it does require you to use Spring.NET to manage your objects, which is out of the scope of this section. For the sake of completeness, I will show you what a declarative aspect definition would look like in this case, but you will have to refer to the Spring.NET Reference Manual for more details:

<aop:aspect name="MethodInvocationLoggingAspect">   <aop:advice     type="LoggingAspectDemo.MethodInvocationLoggingAdvice"     pointcut=       "class(LoggingAspectDemo.Calculator) and method(*)"/> </aop:aspect>


This will cause the Spring.NET container to apply the advice we created to all the methods of all instances of a Calculator class.

Just to give you a feel for the power of the declarative approach and AOP in general, this is all you would have to change in the previous declaration to apply our logging advice to all the methods of all the classes in the LoggingAspect-Demo namespace:

<aop:aspect name="MethodInvocationLoggingAspect">   <aop:advice     type="LoggingAspectDemo.MethodInvocationLoggingAdvice"     pointcut="class(LoggingAspectDemo.*) and method(*)"/> </aop:aspect>


Adding State and Behavior to an Existing Class

The second example we are going to cover uses a combination of introduction and before advice to implement and enforce object locking.

Let's say that you have domain objects in your application that you need to be able to lock within a session. When the object is locked, every attempt by other sessions to modify its state should result in a LockViolationException being thrown.

Using the OOP-only approach, you would add necessary state and methods to classes that need this behavior either directly or by making them inherit from some base class that implements core locking and unlocking functionality.

The former solution is bad because it results in a lot of duplicate code. The second solution is OK as long as the classes that need locking behavior belong to the same class hierarchy and all of them need to support locking. If they belong to different hierarchies, or if you don't want to introduce locking behavior into them all, you are back to square one.

A much better approach is to leverage AOP features and declaratively add locking support only to the classes that need it. This can be easily accomplished using a combination of introduction and before advice.

Implementing Locking Introduction

The first step in this case is to create an introduction that will be used to add state and behavior to advised classes. In order to do that, we need to define an interface that we want to introduce.

namespace LockableAspectDemo {     public interface ILockable     {         void Lock();         void Unlock();         bool IsLocked { get; }     } }


The next step is to create an introduction class that implements this interface. In this example, we will assume that we are writing an ASP.NET Web application and will use HttpContext.Current.Session.SessionID as a session ID for locking purposes.

using AopAlliance.Aop; namespace LockableAspectDemo {     public class LockableIntroduction : ILockable, IAdvice     {         private bool isLocked;         private string sessionId;         public void Lock()         {             isLocked = true;             sessionId = HttpContext.Current.Session.SessionID;         }         public void Unlock()         {             isLocked = false;             sessionId = null;         }         public bool IsLocked         {             get             {                return isLocked && sessionId                    != HttpContext.Current.Session.SessionID;             }          }      } }


So far we've created an introduction that we want to use. Now we need to create an advice that will enforce the lock implemented by this introduction.

Implementing a Lock Enforcement Advice

In this particular case, we don't need a full-blown around advice, such as the one we used in the logging example. All that our advice needs to do is to check whether the target object is locked and throw an exception if it is.

For that purpose, we can use a somewhat simpler before advice, which doesn't require us to make a call to IMethodInvocation.Proceed():

using System; using System.Reflection; using Spring.Aop; namespace LockableAspectDemo {     public class LockEnforcerAdvice : IMethodBeforeAdvice     {     public void Before(MethodBase method, object[] args,     object target)     {         ILockable lockable = target as ILockable;         if (lockable != null && lockable.IsLocked)         {            throw new LockViolationException             ("Attempted to modify locked object.", target);         }      }     } }


Applying Locking Aspect to Domain Objects

The last step is to apply the introduction and advice we defined earlier to domain objects that we want to make lockable. For the purpose of this example, let's say that we want to make the Account class and all its descendants lockable.

First, let's create a sample Account class.

namespace LockableAspectDemo.Domain {     public class Account     {         private string name;         private double balance;              public Account() {}         public Account(string name, double balance)         {             Name = name;             Balance = balance;         }         public virtual string Name         {             get { return name; }             set { name = value; }         }         public virtual double Balance         {             get { return balance; }             set { balance = value; }         }         [StateModifier]         public virtual void Withdraw(double amount)         {             balance -= amount;         }         [StateModifier]         public virtual void Deposit(double amount)         {             balance += amount;         }     } }


We'll omit descendants for brevity, but we can assume that locking also needs to be applied to the CheckingAccount, SavingsAccount, and MoneyMarketAccount classes that inherit from our base Account class.

There are several things that make this class suitable for proxy-based AOP. First of all, properties are implemented using a combination of private fields and property getters and setters. If you used public fields instead, it would be impossible for an AOP proxy to intercept property access.

Second, all properties and methods are declared as virtual. This allows Spring. NET to create a dynamic proxy by inheriting the Account class and injecting interception code. If methods were final, the Account class would have to implement an interface in order to be proxied using the composition proxy. If neither interface nor virtual methods were present, it would've been impossible to create a proxy for the Account class and apply AOP advices to it.

The final thing to notice is the usage of the StateModifier attribute. We defined this attribute primarily to show how advices can be applied based on attributes, but this is also a case where using an attribute makes a lot of sense. It provides additional metadata that makes it very obvious which methods modify object's state.

We could've used the StateModifier attribute in front of property setters as well, but in my opinion that is somewhat redundant. In addition to that, omitting StateModifier in the case of properties gives us an opportunity to show you how to define a composite pointcut.

While it is possible to configure the locking aspect using a code-only approach, it is somewhat cumbersome, so we'll show you only the declarative approach in this case.

<aop:aspect name="LockEnforcementAspect">   <aop:pointcut name="StateModifiers"     expression="class(Account+)       and (setter(*) or attribute(StateModifier))"/>   <aop:advice type="LockableAspectDemo.LockableIntroduction"     pointcut="class(Account+)"/>   <aop:advice type="LockableAspectDemo.LockEnforcerAdvice"     pointcut-ref="StateModifiers"/> </aop:aspect>


Most of the declaration should be self-explanatory. Things that deserve attention are

  • The plus sign behind the class name in the pointcut definition specifies that the pointcut should match the specified class and classes derived from it.

  • The expression "setter(*)" is used to match all property setters, regardless of their type and name.

  • The expression "attribute(StateModifier)" is used to match all the members that are marked with the StateModifier attribute.

Finally, we need to create a client that will leverage the introduced ILockable interface the same way as if it was implemented directly by the Account class.

using LockableAspectDemo.Domain; namespace LockableAspectDemo.Services {     public class AccountManager     {         public void TransferFunds(Account from, Account to             , double amount)         {             ILockable acctFrom = from as ILockable;             ILockable acctTo = to as ILockable;             try             {                acctFrom.Lock();                acctTo.Lock();                from.Withdraw(amount);                to.Deposit(amount);             }             finally             {                acctFrom.Unlock();                acctTo.Unlock();             }         }     } }


As you can see, by using AOP we can truly modularize the object locking feature and apply it wherever appropriate without the shortcomings of the pure OOP approaches.

Note

While the previous example is technically no different than if we used lock statements to synchronize access to acctFrom and acctTo from the multiple threads, there is a big difference in the semantics between the two. With ILockable we could keep the lock for the duration of the multiple HTTP requests, not just for the single request.


Moving Business Rules into Aspects

The final example we are going to show in this section demonstrates a somewhat advanced AOP usage. We do not recommend that you start experimenting with AOP by moving all your business rules into aspects. It is probably the best idea to introduce AOP into your projects slowly by making them handle obvious crosscutting concerns, such as logging, security, and transactions. However, we feel it is important that you understand its full potential so you can apply it effectively when you become more familiar with it.

Every business application has various business rules that are typically embedded into the application logic. While this works just fine for most rules and most applications, it makes them less flexible than they need to be and usually requires code modification when business rules change. By moving some of the business rules into aspects, you can make the application much easier to customize. Unlike in previous examples, removing code duplication is usually not the main reason you would move business rules into aspectsthe primary driver in this case is increased application flexibility.

The best candidates are rules that cause secondary logic to be implemented along with the core logic. For example, the core logic of the Account.Withdraw() method from the previous example is to reduce the account balance. Secondary logic would be to enforce minimum balance requirements or to send an email alert to the account holder when balance falls below a certain amount.

Let's see what our Account class would look like if we added both minimum balance enforcement and a balance alert to it.

using System; namespace BusinessRulesDemo.Domain {     public class Account     {         private AccountHolder accountHolder;         private string name;         private double balance;         private double minimumBalance;         private double alertBalance;         // Spring.NET IoC injected collaborators         private INotificationSender notificationSender;         public Account() {}         public Account(string name, double balance)         {             Name = name;             Balance = balance;         }         public virtual Person AccountHolder         {             get { return accountHolder; }             set { accountHolder = value; }         }             public virtual string Name         {             get { return name; }             set { name = value; }         }         public virtual double Balance         {             get { return balance; }             set { balance = value; }         }         public virtual double MinimumBalance         {             get { return minimumBalance; }             set { minimumBalance = value; }         }         public virtual double AlertBalance         {             get { return alertBalance; }             set { alertBalance = value; }         }         // injected by Spring.NET IoC container         public virtual INotificationSender NotificationSender         {             set { notificationSender = value; }         }         [StateModifier]         public virtual void Withdraw(double amount)         {             if (balance - amount < minimumBalance)             {                 throw new MinimumBalanceException();             }                   balance -= amount;             if (balance < alertBalance)             {                notificationSender.SendNotification                (accountHolder.Email,                "Low balance",                "LowBalance.vm", this);             }         }         [StateModifier]         public virtual void Deposit(double amount)         {             balance += amount;         }     } }


While you could argue that the minimum balance check should remain in the Account class, it is obvious that the notification sending code is not the primary functionality of the Withdraw() method, which makes it a great candidate for an aspect. Let's refactor it.

Creating a Notification Advice

First, we'll create a generic advice that will be used to send notifications.

using System.Reflection; using BusinessRulesDemo.Domain; using Spring.Aop; namespace BusinessRulesDemo {     public class AccountBalanceNotificationAdvice :         IAfterReturningAdvice     {         private INotificationSender sender;         private string subject;         private string templateName;         public INotificationSender Sender         {             get { return sender; }             set { sender = value; }         }                public string Subject         {             get { return subject; }             set { subject = value; }         }         public string TemplateName         {             get { return templateName; }             set { templateName = value; }         }         public void AfterReturning(object returnValue,             MethodBase method, object[] args, object target)         {             Account account = (Account) target;             if (account.Balance < account.AlertBalance)             {                 sender.SendNotification(                              account.AccountHolder.Email,                              subject, templateName,                              target);             }         }     } }


In this example we are using an after advice that checks if the new balance is below the alert balance level and sends a notification if it is. We also defined several properties on the advice class in order to show how you can configure advices using the Spring.NET IoC container.

Tight coupling between the advice and our Account class is not a problem in this case because we know that our advice will only apply to instances of the Account class anywaywe are simply using the advice to move the notification sending rule outside the core logic of the Account class.

What this really buys us is flexibility. If we sell the application to another client who wants us to remove the notification we can easily do it by removing the advice application from the configuration file. Similarly, if a client asked us to implement the logic in such a way that automatic transfer from a Line of Credit account happens whenever the balance falls below a minimum amount allowed, we could easily implement another advice and apply it to our Account class instead, or even in addition to the notification advice.

Applying the Advice

Just like in the previous example, we will only show the declarative aspect configuration to complete this example.

<aop:aspect name="BalanceNotificationAspect">   <aop:advice     type="BusinessRulesDemo.AccountBalanceNotificationAdvice"     pointcut="class(Account+) and method(Withdraw)">     <aop:property name="Sender" ref="NotificationSender"/>     <aop:property name="Subject" value="Low Balance"/>     <aop:property name="Template"       value="~/Templates/LowBalance.vm"/>   </aop:advice> </aop:aspect>


The only thing worth pointing out here is that you can use Spring IoC features to configure your advices, just like you would use it to configure any other object. In this example, we are using that to specify the notification subject and the filename of the NVelocity message template, as well as the instance of the notification sender to use.

Summary

AOP can help you solve some of the problems you are facing every day. While it would be great to have support for AOP in .NET on the language and CLR level, there are tools in the .NET open source community that can give you a decent set of AOP features today.

Probably the best way to start learning more about AOP is to download some of these tools and experiment with them, and maybe even start introducing them into real-world projects to help with the basic usage scenarios, such as logging or profiling. As you become more knowledgeable about AOP and more familiar with the tool of your choice, you will likely find many other areas of your application where aspects can help you.

Thanks, Aleksandar!

So by applying AOP to the problem of Repository design as I talked about before Aleksandar's section, some interfaces to introduce for CustomerRepository could be GetByIdable, Deletable, and so on.

Pretty neat, don't you think? And of course there are more obvious things to use AOP for, such as the ordinary example of logging. It's a bit boring, as Aleks pointed out, but after all a very useful technique for solving a common problem.




Applying Domain-Driven Design and Patterns(c) With Examples in C# and  .NET
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET
ISBN: 0321268202
EAN: 2147483647
Year: 2006
Pages: 179
Authors: Jimmy Nilsson

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net