CQRS and eventsourcing with API Platform IV
February 18, 2019
Here comes Part IV of the tutorial.
Part I was about Setup the API Platform and CLI tools.
In Part II we have implemented user management and JWT-Authentication.
Part III was explaining the domain and implementing the main model objects.
In this chapter we will implement the CQRS-Pattern.
All changes to part 3 you can find here.
Part IV: Implementing the CQRS pattern with Symfony Messenger
The CQRS pattern is well explained by Martin Fowler and Greg Young.
The main idea behind is the separation of write and read operation, where write (or mutation) actions are called commands and read operations are called queries. You can find a very useful post from Romain Pierlot on Medium.
It’s a neccessary step to take on the road to event sourcing, and event sourcing does not work without CQRS. But we can implement CQRS without event sourcing, which is the main goal of this fourth part of the tutorial.
What do we need to implement the CQRS pattern?
- an inbox for the commands, acting as a REST API endpoint and controller, receiving POST requests
- commands wich are triggering actions in the system
- command handlers which are excuting these actions
- a command bus which is delivering the commands to their respective handlers
Additional packages
For this I’m installing the Symfony Messenger Component:
$ docker-compose exec php composer require symfony/messenger
I have created the simplest version of a command class, where all other commands should inherit from. A command class contains three parts:
Metadata
The metadata is a key-value store in the command, where additional informations can be stored and transported. In this case I’am sending the user-role within the command, so the CommandHandler get’s all neccessary informations without using user-services.
Payload
The payload is an array which will be json-serialized for transport.
You can think about object-serializing but for security reasons I’m trying to avoid this.
Validation
For this I have added for simplification the abstract method assertIsValidPayload and can be done with the Assertion Library from Benjamin Eberlei:
$ docker-compose exec php composer require beberlei/assert
In a bigger project you can switch to a JSON schema validation for example Swaggest JSON schema implementation for PHP. If needed we can implement this in one of our next tutorials.
Implementing the inbox controller [1]
The inbox for commands is called messagebox, listening to POST requests on the /api/messagebox endpoint.
You can find the route configuration in the annotations.
This controller does a request validation with the following steps:
- content-type Validation
- command name validation
- payload validation
The command names are mapped to their respective command classes in a whitelist-array in the controller.
This is useful with a small amount of commands. With more commands you can create a lookup-service using tagged services.
If this works well, the command can be created from payload and enhanced with user data before sending to the command bus.
Implementing domain commands and command handlers
Commands and handlers are implemented in domain specific namespaces and folders.
The convention is here:
Namespaces
- App/Domain/{Domain}/Command/{CommandName}Command
- App/Domain/{Domain}/CommandHandler/{CommandName}CommandHandler
Folders
- src/Domain/{Domain}/Command/{CommandName}Command.php
- src/Domain/{Domain}/CommandHandler/{CommandName}CommandHandler.php
This convention makes it easy to tag the command handlers in services.yaml:
command_handlers:
namespace: App\Domain\
resource: '../src/Domain/*/CommandHandler'
tags: ['messenger.message_handler']
For this example I have implemented each of three services as one domain with their proper commands and handlers.
Dispatching commands from CLI
To dispatch a command from CLI, relevant metadata has to be enhanced in the CLI command. For example creating a new user from CLI dispatches \App\Domain\User\Command\CreateUserCommand::fromParams to the messagebus here.
Tests
The tests for each command can be found here, here and here.
Whats next
In the next section we will implement the eventsourcing pattern.
Footnotes
[1] A special thanks goes to the Alexander Miertsch and Sandro Keil from prooph software, who shared their knowledge and experience with us in scientific web development and DDD