Going into CQRS with PHP

Going into CQRS with PHP

John sits alone behind the computer in the office. The only light is the lamp and laptop on his desk. He feels exhausted, however he knows that he must find the cause of the problem.

After another hour of debugging, John randomly reproduced the bug and he finally got it.
In his code he has reused Product Service to retrieve Product from the database.
What he did not knew however, that this code was changing and saving products amount of views, whenever somebody called it.
John had bad luck, as his team mate, introduced a bug in this code which stored incorrect value.

How many times you had to debug the code deeply, when function that seems to be obvious in what it does, was actually doing something unexpected?

The CQRS comes to help us in such situations. So we can reason more about the code, as the code becomes more clear and simple.


Enter the Command Query Responsibility Separation (CQRS)

The CQRS in his principles defines distinction between queries and commands.
Queries are way to fetch the data and Commands to modify it.

This simple distinction allows for agreement between developers, that they are safe to use
queries as many times as they want without side effects being done.

Queries and Commands are identifiable, which means we know what specific query or command is supposed to do. This has huge advantage over CRUD, as it allows
to catch the intention of the user.
If user wants to change his email address. Then we may ask only for the email and as a result, send him specific notification to confirm his new email address.
From the query side, we can provide queries for specific views. This allow us to know where is it used and what decision it precede. For changing an email, it may be enough to retrieve only the current email, instead of full user details.


CQRS in Practice

Symfony CQRS

To install Ecotone support for Symfony, do following:

Symfony
composer require ecotone/symfony-bundle

Laravel CQRS

To install Ecotone support for Laravel, do following:

Laravel
composer require ecotone/laravel

Registering Command


Let's define our first Command that will change the email address.

class ChangeEmailAddressCommand
{
    public function __construct(private string $userId, private string $email) {}

    public function getUserId(): string
    {
        return $this->userId;
    }

    public function getEmail(): string
    {
        return $this->email;
    }
}

The Command Handler is a place where we actually handle the defined command.

class UserService
{
    #[CommandHandler]
    public function changeEmail(ChangeEmailAddressCommand $command) : void
    {
        // retrieve user and change the email
    }
}

This is how we correlate the Command with the Handler.
Ecotone is looking at the first type hint in the method declaration to know, which command should this handler use.

To send this command, we will use CommandBus, that we will inject into our HTTP Controller.

class PersonController
{
    public function __construct(private CommandBus $commandBus) {}
    
    public function changeEmailAddress(Request $request)
    {
        $userId = $request->get('userId');
        $email = $request->get('email');
        
        $this->commandBus->send(new ChangeEmailAddress($userId, $email));
        
        return new Response();
    }
}

The Command Bus is automatically registered within your Dependency Container. He is aware of the routing to specific handlers, so all you need to do is to send the command.

Above Controller is pseudo code of course, adjusting it to Symfony or Laravel should be pretty straightforward however.  

Registering Query


Let's register Query that will return Shipping Address of the user.

class GetUserShippingAddressQuery
{
    public function __construct(private string $userId) {}

    public function getUserId(): string
    {
        return $this->userId;
    }
}

And now Query Handler that will handle our GetUserShippingAddressQuery.

class UserService
{
	#[QueryHandler]
    public function getPersonDetails(GetUserShippingAddressQuery $query)
    {
        $shippingAddress = // use query to get the shipping address;

        return $shippingAddress;
    }
}
Ecotone allows for full flexibility, you may define multiple query/command handlers within same class, if you feel a need.
This helps in keeping your logic together and avoiding code boilerplate.

As we have registered our Query Handler using #[QueryHandler] annotation, we may now send the query.

class PersonController
{
    public function __construct(private QueryBus $queryBus) {}


    public function getShippingAddress(Request $request)
    {
        $userId = $request->get('userId');

        $shippingAddress = $this->queryBus->send(new GetUserShippingAddressQuery($userId));

//        serialize if needed and return response

        return new Response($shippingAddress);
    }
}

Now we are calling the QueryBus and returning the billing address.


Powerful capabilities, yet simple usage


When you will start using Ecotone, you will find yourself doing much less configuration,
in result code will become much easier to read and understand.

Ecotone follows new trend in programming, that aims for keeping the business logic clean of framework. In most of the cases, you will be using PHP 8 Attributes only, and Ecotone will handle firing and wiring it all together.

Stay tuned for next blog post about using Events in your system.

> >