Skip links

Separating concerns even better with events

March 9, 2015 at 9:03 AM - by Freek Lijten - 2 comments

Tags: , ,

Object oriented architecture interests me. A lot is happening in PHP over the last few years. PHP might be a decade late but hey, it is happening. I feel a lot of techniques and terms like SOLID, BDD, DDD, CQRS and what not are essentially one thing: tools to help us model the real world in an as useful and understandable way as possible. The concept of an API or a Service is widespread, even  in PHP land. I feel the same goes for repositories. This is a great way of separating concerns, but there is room to improve! In my Services at least :)

At PHP Benelux 2015 I witnessed a talk by Matias Noback about events. It was a great talk so if you have chance to see it yourself somewhere, do so! In a very tiny nutshell he took us from what most would consider already decent code to better code. Lets start with "decent".

Decent Code

Our fictional software has a central Service for user management. When a user is added we want to check some business rules (i.e. a username may only occur once) and delegate storing to an implementation of the UserRepository interface. On success we would like to notify someone of the fact that a new user was added. On top of that we track the ways in which a User in our application has contact with other users or the system.

The code below does a decent job at being understandable and concerns are separated into their own building blocks. The Repository stores data in some way, MessageSender knows how to send emails from an Email object, UserServiceContext has some configuration and there even is a separate API for ContactMoments:

class UserService
{
    private $UserRepository;
    private $MessageSender;
    private $UserServiceContext;
    private $ContactMomentApi;

    public function __construct(UserRepository $UserRepository, MessageSender $MessageSender, UserServiceContext $UserServiceContext, ContactMomentApi $ContactMomentApi)
    {
        $this->UserRepository = $UserRepository;
        $this->MessageSender = $MessageSender;
        $this->UserServiceContext = $UserServiceContext;
        $this->ContactMomentApi = $ContactMomentApi;
    }

    public function saveUser(User $User)
    {
        if ($this->isUserNameAlreadyInUse($User)) {
            throw new UserNameAlreadInUseException($User);
        }

        if ($this->UserRepository->saveUser($User)) {
            throw new PersistenceException($User);
        }

        $Email = new Email();
        $Email->setSubject('new User');
        $Email->setRecipient($this->UserServiceContext->getNewUserMailRecipient());
        //...
        $Email->send();

        $ContactMoment = $this->ContactMomentApi->createContactMoment();
        $ContactMoment->setSubject('User added');
        //...
        $this->ContactMomentApi->saveContactMoment($ContactMoment);
    }
}

Everything is always up for improvement but I think we can agree that this code is not too bad. Still there is more than enough to improve. The saveUser function is all about saving users right? But if you see its internals most of the code is not about saving a user at all. It is mostly about reacting to a saved user in several ways.

Ask yourself: "Why would a UserService even have notion of the concept of email?" Already this class has four dependencies of which it uses only one for the actual saving of Users. The rest is reacting to that saving. They react to an event.

Events?

So lets remodel our application to take into account the fact that a lot is reacting on events. If a User is added this should fire an event. This event needs context. Obvious candidates for context would be the User itself, but there might be other relevant stuff like the current date/time. Maybe we would like to know who added a user, are we talking registration or some administrator entering data? You can probably come up with more yourself.

So a possible Event could look as follows. Note it is called UserAdded instead of UserAddedEvent. The Event keyword would not add anything here.

class UserAdded
{
    public function __construct(User $User, DateTime $AddedAt, //...more context)
    {
        // initialisation
    }

    public function getUser()
    {
        return $this->User();
    }
    
    //...etc.
}

This event is not much more than the context and a description in the form of the class name. Now it needs to be handled. Enter a handler:

interface UserAddedHandler
{
    public function handle(UserAdded $UserAdded);
}

This simple interface can be implemented by concrete Event Handlers that actually do something, like sending the email and storing the contact moment:

class SendEmailWhenUserIsAdded implements UserAddedHandler
{
    private $MessageSender;
    private $UserServiceContext;

    public function __construct(MessageSender $MessageSender, UserServiceContext $UserServiceContext)
    {
        $this->MessageSender = $MessageSender;
        $this->UserServiceContext = $UserServiceContext;
    }

    public function handle(UserAdded $UserAdded);
    {
        $Email = new Email();
        $Email->setSubject('new User');
        $Email->setRecipient($this->UserServiceContext->getNewUserMailRecipient());
        //...
        $Email->send();       
    }
}

This handler now is responsible for one thing, reacting to an added user by sending an email. The dependencies for this (MessageSender, etc) can be removed from UserService since that is no longer the location where email is sent from. Obviously the contact moment part can be extracted in exactly the same way.

All the constructor of our UserService now needs is one or more handlers that react to the UserAdded event:

class UserService 
{ 
    private $UserRepository; 
    private $userAddedHandlers; 
    public function __construct(UserRepository $UserRepository, array $userAddedHandlers) 
    { 
        $this->UserRepository = $UserRepository; 
        $this->userAddeHandlers = $userAddedHandlers; 
    } 

    public function saveUser(User $User) 
    { 
        if ($this->isUserNameAlreadyInUse($User)) { 
            throw new UserNameAlreadInUseException($User); 
        } 
        if ($this->UserRepository->saveUser($User)) { 
            throw new PersistenceException($User); 
        } 

        $UserAdded = new UserAdded($User, new DateTime('now')); 
        
        foreach ($this->userAddedHandlers as $Handler) { 
            $Handler->handle($UserAdded); 
        } 
    }
}

So what did we achieve?

  1. We removed a lot of dependencies that should not have been in the UserService in the first place
  2. The save user function saves a user and exactly that
  3. Our code is separated across concerns. Users are saved, email are sent
  4. Our classes have become smaller, more to the point and therefor more readable.

Taking it further

I can imagine this leaves you with a couple of questions. Where there is a UserAdded there will most likely be UserEdited, UserDeleted and more. You could make a common interface for all handlers, let them decide to which type of event they would like to react. Different options exist for this problem.

Another step forward is taking away the synchronicity. It is likely irrelevant when your software reacts to events in almost all cases. Of course the email should not be sent next week, but a fifteen minutes delay is most likely no problem.

In the case of asynchonously handled events you're going to need a bit more infrastructure, but in your code the changes might be minimal. A great step ahead would be to remove the dispatching from the UserService into its own object. From then one, dispatching is a concept to API's and it no longer matters in what way events are actually handled.

Finally, some objects in this example should probably be restructured into smarter models, but that falls outside of the scope of this post.

I hope this helpes someone somewhere, happy coding!

Share this post!

Comments

  1. Pablo MedinaPablo Medina Wrote on March 9, 2015 at 4:18:36 PM

    Small typo on line 8:

     $this->userAddeHandlers

    should be:

     $this->userAddedHandlers

  2. Rasmus SchultzRasmus Schultz Wrote on March 10, 2015 at 8:26:54 AM

    Very good!

    Now to take this one step further, I would separate out the concern of sending and listening for event notifications, into a centralized event bus. This can help with some performance concerns and may provide a useful means of globally monitoring all events during development.

    I wrote this very small and simple event bus, which you may find useful:

    https://github.com/mindplay-dk/funnel

Leave a comment!

Italic and bold

*This is italic*, and _so is this_.
**This is bold**, and __so is this__.

Links

This is a link to [Procurios](http://www.procurios.nl).

Lists

A bulleted list can be made with:
- Minus-signs,
+ Add-signs,
* Or an asterisk.

A numbered list can be made with:
1. List item number 1.
2. List item number 2.

Quote

The text below creates a quote:
> This is the first line.
> This is the second line.

Code

A text block with code can be created. Prefix a line with four spaces and a code-block will be made.