Alexander Steshenko | On Software Engineering

Aspect Oriented Programming and Cross-cutting concerns

Let’s say I’m working on an application that has “users”. First, I’m trying to design and implement business model, leaving MVC and data storage for later.

class UsersManager // or, could be UsersService
{
    /**
     * All registered users
     *
     * @var array
     */
    private $users = [];

    /**
     * Registers a user in the system
     *
     * @param string $firstName
     * @param string $lastName
     * @param string $email
     */
    public function registerNewUser($firstName, $lastName, $email)
    {
        $this->users[] = new User($firstName, $lastName, $email);
    }
}

class User
{
    /**
     * The user is not activated by default
     *
     * @param string $firstName
     * @param string $lastName
     * @param string $email
     * @param bool $activated
     */
    public function __construct($firstName, $lastName, $email, $activated = false)
    {
        // ...
    }
}

When users are registered in the application they are considered inactive until they have verified their email address.

While the DATA team is still picking the most trending data storage for the application, I implement the logging system. Basically, we need to write to log each time a new user is added: 

public function registerNewUser($firstName, $lastName, $email)
{
    $this->users[] = new User($firstName, $lastName, $email);
    $this->logger->log('New user created: ' . $email); // only dumping the email to the log
}

Now, I want the administrator of the site to be able to create activated users, so that email verification is not required. The same log message should be added to the log file: 

/**
 * Could be Zend_Log or Monolog or whatever, let's say it was instantiated in __constructor
 *
 * @var Zend_Log
 */
private $logger;

/**
 * Registers a user in the system
 *
 * @param string $firstName
 * @param string $lastName
 * @param string $email
 */
public function registerNewUser($firstName, $lastName, $email)
{
    $this->users[] = new User($firstName, $lastName, $email);
    $this->logger->log('New user created: ' . $email);
}

/**
 * Adds an already-activated user
 *
 * @param string $firstName
 * @param string $lastName
 * @param string $email
 */
public function addActivatedUser($firstName, $lastName, $email)
{
    $this->users[] = new User($firstName, $lastName, $email, true);
    $this->logger->log('New user created: ' . $email);
}

It’s not too bad now, but the duplication of code has begun. It gets worse when you’re ready to save the users set in a database: 

/**
 * Some database abstraction, may be a table gateway, or, Doctrine's Entity Manager
 *
 * @var Database
 */
private $db;

/**
 * Registers a user in the system
 *
 * @param string $firstName
 * @param string $lastName
 * @param string $email
 */
public function registerNewUser($firstName, $lastName, $email)
{
    $user = new User($firstName, $lastName, $email);
    $this->users[] = $user;
    $this->db->insert('users', $user->toArray());
    $this->logger->log('New user created: ' . $email);
}

/**
 * Adds an already-activated user
 *
 * @param string $firstName
 * @param string $lastName
 * @param string $email
 */
public function addActivatedUser($firstName, $lastName, $email)
{
    $user = new User($firstName, $lastName, $email, true);
    $this->users[] = $user;
    $this->db->insert('users', $user->toArray());
    $this->logger->log('New user created: ' . $email);
}

With all the code duplication and mixing of things, I can say good bye to the nice separation of concerns.  

Cross Cutting Concerns 

Why are they “cross-cutting”? Because they exist in different layers of your application. They cross the boundaries, if you will. In the example above, “adding a user” may refer to three things: busines-wise adding a user, saving a new record to a RDBMS table or adding a line to a log file. 

Business rules. MySql table. Log file. They are all different. Yet they are one: “adding a user”.  This is why they are “cross-cutting”. Why is it called a problem of cross-cutting concerns? Because there is no “built-in” way of solving this in classic paradigms and approaches to programming. 

OOP abstractions 

Having OOP abstractions helps to some degree. For instance, instead of working with database directly we introduce new concepts into the Business area. $users collection may be a “repository” still acting like an array, it may reference the database under the hood. The so-called UnitOfWork pattern helps dealing with things like DB transactions. 

In reality, however, it is pretending. You can have a business requirement about users, but not about “units of work”. These are all made-up entities that you have to introduce into your design again and again to justify/hide the presence of infrastructure-related logic (db/log/…) in your Domain Layer. 

Aspect Oriented Programming (AOP) 

The UsersManager class is responsible not only for the business “aspect” of the application but also deals with database and logging systems. These are the three different aspects of the same activity, that should not be mixed up. 

Aspect Oriented Programming is an approach that helps separating these aspects within your application. The idea is that you can store your aspects independently, while they will still be executed “at the same time” (or, you can define a particular order of execution). 

As AOP contradicts the OOP in its classic definition, implementing it usually implies relying on “magic” features of a language. 

In some languages it is built-in (although still doesn’t feel like it is), but in most cases it’s done by a framework, an extension or a library. In PHP, what’s worth mentioning is the Go framework by lisachenkoAOP extension for PHP and the set of tricks used in Doctrine2

Implementations may differ depending on which tool you use, but the idea is as follows. I still have the business layer UsersManager as it was in the beginning, independent from Db and Logging: 

class UsersManager
{
    /**
     * All registered users
     *
     * @var array
     */
    private $users = [];

    /**
     * Registers a user in the system
     *
     * @param string $firstName
     * @param string $lastName
     * @param string $email
     */
    public function registerNewUser($firstName, $lastName, $email)
    {
        $this->users[] = new User($firstName, $lastName, $email);
    }

    /**
     * Adds an already-activated user
     *
     * @param string $firstName
     * @param string $lastName
     * @param string $email
     */
    public function addActivatedUser($firstName, $lastName, $email)
    {
        $this->users[] = new User($firstName, $lastName, $email, true);
    }
}

Then I have the aspect of logging, something like this:

class LoggingAspect
{
    private $logger;

    public function addUser(User $user)
    {
        $this->logger->log('New user created: ' . $user->getEmail());
    }
}

And the aspect of database interaction:

class DbAspect
{
    private $db;

    public function addUser(User $user)
    {
        $this->db->insert('users', $user->toArray());
    }
}

Then there would be some initialization logic, wiring all the aspects together:

class AopBootstrap
{
    public function bootstrap()
    {
        // the following is highly fictional, just to illustrate the idea
        $addUser= new Concern('UsersManager', '$users[]='); // whenever an element is added to $users array...

        $addUser->addAspect(new LoggingAspect());
        $addUser->addAspect(new DbAspect());

        $this->initConcern($addUser);
    }
}

Why doesn’t everybody use it?

There is a lack of tools at the moment, and the ones we have are limited, not convenient enough to use and hacky due to the magic features they utilize:

  1. In theory, we may need to have aspects of any random piece of logic. With the tools we have we’re limited to having aspects of a method or a function. 
  2. Aspects should be made first-class citizens. I don’t think introducing an alias for “class” is enough. 
  3. Understanding what an AOP application does is tricky. You can’t see right away all the aspects of some code you’re looking at.
  4. OOP requires experience, well-thought hierarchy of classes and SOLID design in order to be understood correctly. The situation is the same with AOP, but there’s not much in terms of best practices. 
  5. No IDE support. For example, we could have support for swithing between contexts on any given line of code and seeing them in some manner.

I like analogy where OOP/procedural programming is 2-dimensional while adding AOP into the equation makes it 3d. It does require better imagination. 


Comments and feedback are much appreciated. See contact details here