11 June, 2019
In the previous parts of the article about CQRS and Event Sourcing implementation in PHP, I’ve focused on the main idea of CQRS and Event Sourcing. There was a presentation of their pros and cons. Then, I’ve mentioned the implementation of the Write Model with Broadway library in the second part. Now, we’re slowly coming to an end of this article. In the third part, I’ll try to explain how the implementation of the Read Model looks like.
Before I’ll step further and focus on the implementation of the Read Model, I think it’s good to revise the requirements set in the previous part of the article. “All the users need to be able to log in and browse through the list of all the registered users”. It’s worth specifying that a name, a surname and a username should be displayed on the list of the users.
In accordance with these requirements, you should prepare two models of the user. The first will be created for the authentication purposes – for a person trying to sign in to the system. The second model will be created to display the user on the list.
In the “classical” approach, most probably you would decide to use the same class for both cases. However, thanks to CQRS, it’s possible to split these two functions into two separate classes. It helps to customise them and to make sure they will only include the elements which are necessary to realise their tasks. Consequently, you have to implement two classes:
- SecurityUser – for the authentication purposes,
- User – for the users’ list display purposes.
Let’s start with checking how these models look like. Then, you can focus on how to create them.
Let’s assume that the app is based on Symfony. That’s why it’s necessary to implement its interface ‘Symfony\Component\Security\Core\User\UserInterface’ to use it during the authentication.
Besides the Symfony requirements, it’s necessary to implement a serialization interface which is required by Broadway. Thanks to that, Broadway will take care of writing and reading from a database.
As per the interface requirements, you’ll need:
- user name,
- encrypted password,
- user roles.
Consequently, SecurityUser class will look like the one on the example below.
You probably noticed on the listing, that the class has only a minimal data set. This data is necessary to display the list and to meet the requirements of the interface SerializableReadModel from Broadway library. You can skip the data like password or roles at this point. It’s because you don’t need it to display the list. Also, thanks to that, you minimise the risk of any sensitive data leaks.
There are two user classes, which are independent and are used in different contexts. Now, you need to create these objects. In the Write Model, you create UserWasCreatedEvent which is propagated through EventBus. Read Model can read and handle these events.
A projector is a listener, it’s looking for the events issued by Event Bus. So, what are the requirements which need to be met by a projector? Name of a method which handles an event must have a special construction: “apply” + a name of an event class. In analysed case, it’s applyUserWasCreatedEvent. Also, a method needs to take a certain event as one and only parameter.
In this method, we create objects SecurityUser and User, based on the passed event and then, you write them through dedicated repositories. To simplify the example, we create both events in the same projector. It’s also possible to implement two separate projectors for each model. To write objects, you use two dedicated repositories. Let’s take a look at how to create them.
A repository can be created in a relatively easy way, using a factory provided by Broadway. You need to name a repository and set what kind of objects it has to handle. Also, you need to decide where you want to store the data. Broadway provides a solution for Elasticsearch and MongoDB. An example of a repository code can look like the one below.
Of course, you can also use your own implementation if the Broadway support is not enough for you or you want to use a different database.
A controller which handles users’ list display
The data about a newly created user is added to the repository. Now, you can use it. As you remember – one of the presumptions was to display the users’ list. The app is based on Symfony, that’s why you need to create a controller class. It will be responsible for handling this kind of requests. Now you need to inject the relevant repository and then, get the users from out there. Next step is to pass it through to the view. In the example below, it’s HTML, rendered from the Twig template.
Use of SecurityUser objects
The second requirement was the users’ authentication. You need to create UserProvider class which implements Symfony\Component\Security\Core\User\UserProviderInterface.
In this example, you need to inject the relevant repository which provides access to the SecurityUser objects. Also, you need to implement the methods which are required by the interface.
The implementation can look like the one on the example below.
After indicating this class as a provider in security.yaml file, you can fully enjoy having independent models and you can appreciate the other advantages of CQRS.
So, your models are independent and can be stored in different databases. In the presented scenario, any changes in display won’t affect security. If you want to add any new data to be displayed on the users’ list, you can do it without touching the part of the code which is responsible for authentication. It reduces the possibility of mistake which could weaken the security or disable signing in.
Another advantage is that your models can be tailored exactly to your needs. If you have a few users’ lists, with different range of the display fields and different range of filters, you can use separate models to all the lists. Of course, you should stay reasonable and remember not to create too many classes, because it would mean way more code to maintain. Everything should be matched to the project’s requirements.
You probably noticed that thanks to the Broadway library, you don’t have to write too much code when implementing the Read Model. You also have two different methods of storing this data (ElasticSearch and MongoDB). Thanks to that, you can easily add new models and views and the small amount of code helps with its maintenance.
That’s all about the implementation of the Read Model. In the next (and at the same time – the last) part of the article, I’ll focus on the most common issues connected to CQRS and Event Sourcing. I’ll also present some ideas on how to deal with them.