Implementing a use case (IV) — Domain Events (II)

Maikel González Baile
4 min readOct 4, 2018

--

This post is part of Implementing a Use Case, a series of posts where I share my learnings on designing, architecting and implementing use cases. I suggest reading the previous posts where I already explained some concepts you might find here.

You can either take a look at the code of the application example or the different tags which show the state of the application at the moment of the post.

In the previous post I explained the concept of a Domain Event, let’s see an approach to include it in our code. First of all, Domain Events must be represented as first-class citizens in your code:

├── Application
├── Domain
│ ├── Event
│ │ ├── PullRequestCreated.php
│ │ └── PullRequestReviewed.php

Let’s see now how we can manage the events within our code. First, we can see a couple of modifications in our Command Handler that creates a pull request:

Notice that now the PullRequest class returns events which are saved throughout the Repository. If we look at the PullRequest class:

We see that the create method checks the same invariants that were checked in the previous version with the only difference that, in case of an error, a Domain Event is returned with the info of the problem. Otherwise, a PullRequestCreated event is returned.

At this point, many people are might wondering “but where is the state? where is the Entity that I normally persist in the database?”. Let’s forget about everything related to databases and keep the focus on our business rules for the moment. They say that a writer can create a PR if certain invariants are passed, or fail otherwise. Our tests should prove it:

Let’s see now which changes I had to do to reach the approaches shown in this post.

Tests explicitly show what is the intent and which facts can happen depending on the business rules. Let’s take a quick look into another use case, assigning a reviewer to a PR (Command Handler and Pull Request code are shown below together):

Let’s see now which changes I had to do to reach the approaches shown in this post.

In this case, we first fetch the Pull Request from the Repository and then execute the behavior. Let’s see the test:

So far we have modeled two different use cases and I think the code is very self-explanatory, easy to read and understand.

Notice that in the case of assigning a reviewer we are fetching a State from the Repository, how is that possible if we saved Domain Events? Well, since the Domain Events have all the information inside we can just extract that information from them and persist it in a Table in the database, so that when we want to hydrate the State we just need to query for that data in the same way we would do it if we had extracted the info from a regular Entity class.

I am not going to go into the implementation details of how to persist the information because it can be done in many different ways: just call the getters on the Domain Event to extract the fields and insert the values into a Table in the form of a Row; if you use an ORM you can extract the info from the events and copy it into an Entity that is mapped with the database; you can use plain SQL or some lightweight library to perform the insert; etc.

In addition to saving the info in the form of a state in a table, it is important that you also append the Domain Events in a separated table known as the Event Store. The Event Store is the table that keeps the log of your application with all the facts that have occurred in your system. Remember that a database transaction is started at the beginning of the Command Bus, meaning that both appending the events in the event store and persisting the events info in the form of a state will either succeed or fail altogether. This is important to have events and state consistent.

Notice in the image above how the PullRequestState class is thinner than the PullRequest entity. The reason is that we don’t need to hold info in a class just to be persisted anymore, the only info we need in the state is the one that is used to satisfy invariants. The rest of the data is already saved in the database and can be queried at any moment.

In the next post, I iterate over this solution to improve it a little bit more by making use of the known Command Bus and make our code even more readable and easy to maintain.

--

--