Prooph is an Enterprise-ready PHP CQRS and Event Sourcing packages for PHP with support for the most famous PHP web frameworks. If you are not familiar with CQRS (Command Query Responsibility Segregation), it’s first described by Greg Young in 2010 and it’s basically a design pattern used in enterprise and microservices architecture to separate read and writes in your data layer.

prooph is used in a couple of enterprise projects. It runs in dockerized Microservices on Amazon AWS as well as side-by-side with Doctrine ORM in monolithic PHP applications. It powers eCommerce, booking and content management solutions to name a few.

Prooph PHP CQRS

Prooph PHP CQRS features

Prooph come with awesome features, its components are a set of loosely coupled php packages that can be composed to a powerful toolbox. However you may find it a bit complex in the beginning. Some of its features include :

  • Event Sourcing : It is different than what you’ve learned. Explore a fresh new way of designing and developing software with a clear focus on intent, behaviour and domain events.
  • Event-Store : Turn your traditional database into a full-featured event store. No new technology stack required. No magic involved. Just another way to organize and manage data.
  • Snapshot-Store : High-performance write operations without losing the simplicity and scalability of PHP’s Shared Nothing Architecture in an event centric system.
  • Persistent Projections : With persistent projections you can feed event streams directly into read-optimized databases that serve your data at the speed of light.
  • CQRS Service-Bus : Message-based communication between different parts of a system is the basic building block for scalable and maintainable enterprise software.
  • Message Queue : Shift work to background jobs, manage long-running business processes and handle high traffic with seamless message queue integrations.
  • Framework and Database supported : It support Zend Framework, Symfony and Laravel, in addition to the databases MySQL, PostgreSQL, MongoDB, ArangoDB, Redis, and finally the Messaging systems ZeroMQ, RabbitMQ and Bernard.

To install Prooph, it’s a bit funny, the website link to the github page with 77 repositories. Use the getting started link instead, but anyway you can simply install it in your project with composer using :

$ composer require prooph/common

The two most common messages are Command and Query, so our first Hello world will be a Message of type Command

<?php
//All prooph components enable strict types
declare(strict_types=1);

namespace Prooph\Tutorial;

use Prooph\Common\Messaging\Command;
use Prooph\Common\Messaging\PayloadConstructable;
use Prooph\Common\Messaging\PayloadTrait;

//Require composer's autoloader
require 'vendor/autoload.php';

//Our first message
final class SayHello extends Command implements PayloadConstructable
{
    use PayloadTrait;

    public function to(): string
    {
        return $this->payload['to'];
    }
}

$sayHello = new SayHello(['to' => 'World']);

echo 'Hello ' . $sayHello->to();

//Hello World

The PayloadTrait is used here in conjunction with the PayloadConstructable interface to instantiating our command with a Payload – a simple array – and get access to it using within the $this->payload message. While this is a very easy and fast way to create message classes it is completely optional.Ā 

Running this script will simply print “Hello World”, however it shows how to create a SayHello message Command. The most important part here is not the Payload, but the Command which the main class SayHello extends from it.

<?php

declare(strict_types=1);

namespace Prooph\Common\Messaging;

use DateTimeImmutable;
use Ramsey\Uuid\Uuid;

interface Message extends HasMessageName
{
    public const TYPE_COMMAND = 'command';
    public const TYPE_EVENT = 'event';
    public const TYPE_QUERY = 'query';

    /**
     * Should be one of Message::TYPE_COMMAND, Message::TYPE_EVENT or Message::TYPE_QUERY
     */
    public function messageType(): string;

    public function uuid(): Uuid;

    public function createdAt(): DateTimeImmutable;

    public function payload(): array;

    public function metadata(): array;

    public function withMetadata(array $metadata): Message;

    /**
     * Returns new instance of message with $key => $value added to metadata
     *
     * Given value must have a scalar or array type.
     */
    public function withAddedMetadata(string $key, $value): Message;
}

Howto PHP CQRS with Prooph

CQRS PHP

The basic concept for Command Query Responsibility Segregation is very simple and can be illustrated by a common service class UserService which create a user, then get it. So we will have here a command to create user and a query to read user data from database, the classic way is when your code directly access the database :

<?php

declare(strict_types = 1);

namespace Prooph\Tutorial;

class UserService
{
    private $userRepository;

    public function __construct(UserRepository $repository)
    {
        $this->userRepository = $repository;
    }

    public function createUser(int $id, string $name, string $email): User
    {
        $user = new User($id);

        $user->setName($name);
        $user->setEmail($email);

        $this->userRepository->save($user);

        return $user;
    }

    public function getUser(int $id): User
    {
        $user = $this->userRepository->get($id);

        if(!$user) {
            throw UserNotFoundException::withId($id);
        }

        return $user;
    }
}

The CQRS way is a bit different as first of all, the createUser should not return anything, and also we need to separate the read and write methods so the UserFinder will be :

<?php

declare(strict_types = 1);

namespace Prooph\Tutorial;

class UserFinder
{
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    public function getUser(int $id): array
    {
        $userData = $this->connection->findOneBy(['id' => $id]);

        if(!$userData) {
            throw UserNotFoundException::withId($id);
        }

        return $userData;
    }
}

And the createUserHandler

<?php

declare(strict_types = 1);

namespace Prooph\Tutorial;

class CreateUserHandler
{
    private $userRepository;

    public function __construct(UserRepository $repository)
    {
        $this->userRepository = $repository;
    }

    public function handle(RegisterUser $command): void
    {
        $user = User::create($command->userId(), $command->userName(), $command->email());

        $this->userRepository->save($user);
    }
}

With such design pattern, when you create a new user, you cannot redirect immediately to a user detail page with the created user id for example. So here we issued a command to create a user, but we don’t know when and how the user will be created. So in this case if you want to redirect immediately to the user page, you should be looking for a user created with the unique email address for example, with the UserFinder and once you find it, you can redirect to the user page. Or simply redirect to List of users page, where you can immediately get the user Id.

The difference in the PHP CQRS systems is that you can easily scale you application, and if there is any issue with writing data you can still access it in read only mode. Also you can distribute read and write to separate servers. The most interesting part in an event sourced system, is that all state changes are described by events. As long as your system can fire events, it will be serving client correctly.

It’s great to see PHP Businesses running enterprise-level solutions. We suggest you to watch Nomad lightning talks about using an Event Store in PHP

And Oliver Sturm talk at the International PHP conference about CQRS and Event Sourcing

Prooph is an open source software released under a BSD version 3 license. More information at http://getprooph.org/

LEAVE A REPLY

Please enter your comment!
Please enter your name here