Aspect-Oriented Programming (AOP) is a set of methodologies, tools, and approaches that attempt to improve modularity by allowing the separation of cross-cutting concerns. A cross-cutting concern is a part of an application that affect other areas of the program. They generally can not be cleanly decoupled from the other parts of the system and tend to result in either some duplication of code or deep dependencies between the various areas of the system. The cross-cutting concerns are quite common, an enterprise or near-enterprise level application will always have at least one, if not most of these concerns. Typically these items are used the same way throughout the different areas of the software, and include examples such as:
- Error detection and correction
- Data validation
- Transaction processing
- Internationalization and localization which includes Language localization
- Information security
If using AOP in an Object-Oriented (OO) language, it is generally required to create separate objects/classes to manage these cross-cutting concerns. This means that your code base will likely have a section of code that is relegated to providing the classes and methods that are accessed from many other classes. This is certainly something that we are all used to seeing, so it almost seems natural and we can generally loosen our object-oriented paradigms enough to make ourselves think that this is an “ok solution.” And it is. It works. It provides modularity. However, it also puts too much intelligence into those classes that are calling that shared code.
A common workflow that demonstrates this is when a new user registers for a website. The information is saved in the local system’s database. Then an email is sent to the user welcoming them to the site. A message is then sent to the email-list management application to enroll the user into the mailing list. There is also logging going on before each major step. This generally looks something like:
There is nothing really obnoxious about this example, however it shows that there are things going on this method that are really not a concern of the User class. This is a convenient place to put this work, but is it correct? Another solution is to put a Managing class that calls the User.Save method and then handles the orchestration of managing the rest of the work, something along the lines of a UserManager.Save(). However, that means you then likely have a less intuitive route into creating a new user and have implemented a class that will easily become an example of an OO anti-pattern (Anemic Domain Object).
So how can you solve both the requirement that this business process be supported AND not implement any anti-patterns? Is it even possible?
Yupp, it is. One way is through the use of an AOP approach.
Let’s start with some of the AOP vocabulary. It is not necessary for implementation, but more for communication.
Join points - Join Points are those areas in the code that have the cross-cutting concerns. In the example above it includes the EmailManager, the LoggingManager, and the MailingListClient. These points are those areas where it makes sense to add additional functionality.
Point cuts – Point cuts provide a way to determine whether a certain set of functionality matches a particular join point, basically linking a join point to a separate set of functionality.
Advice – Information about whether the code should be running before, during, or after a join point. This code only fires, however, when the point cut is invoked.
Yes, I know this is a confusing set of explanations at this point. At this point let us leave it that join points mark the areas where other stuff can be done, point cuts provide a way to do that other stuff, and advice gives information on when to do that other stuff.
When working in a fully Aspect-Oriented language that is all you need. What this does is tell the compiler how to link the various modules together. In a AO compiler this is called weaving, and is when the compiler knows, through the join points and point cuts what code needs to be weaved from a cross-concern module into a core module (the User class shown above, for example). Weaving this code creates a single file where all of the cross-concerns are managed in a different module yet is considered to be part of the base executable. In short, it is a way to create a compiler “trick” so that the cross-concern code is folded into the core code as marked by the join points.
Can this be done in .NET? Next post will talk about that.