Small convention based CQRS library for PHP

Overview

LiteCQRS for PHP

Small naming-convention based CQRS library for PHP (loosely based on LiteCQRS for C#) that relies on the MessageBus, Command, EventSourcing and Domain Event patterns.

Build Status (Master)

NOTE Use the 1.1 branch, as the dev-master is currently in heavy refactoring.

Terminology

CQS is Command-Query-Separation: A paradigm where read methods never change state and write methods never return data. Build on top, CQRS suggests the separation of read- from write-model and uses the DomainEvent pattern to notify the read model about changes in the write model.

LiteCQRS uses the command pattern and a central message bus service that finds the corresponding handler to execute a command. A command is just a class with some properties describing it, it can optionally implement LiteCQRS\Command.

During the execution of a command, domain events can be triggered. These are again just simple classes with some properties and they can optionally implement LiteCQRS\DomainEvent.

An event queue knows what domain events have been triggered during a command and then publishes them to an event message bus, where many listeners can listen to them.

Changes

From 1.0 to 1.1

  • Extending LiteCQRS\Command and LiteCQRS\DomainEvent is NOT required anymore. In fact you can use any class as command or event. The naming conventions alone make sure command handlers and event listeners are detected.

  • JMS Serializer Plugin cannot "detach" aggregate root properties that are part of an event that is serialized anymore. Putting related aggregate roots into an Event is therefore not supported anymore (and not a good idea even with JMS Serializer 0.9 anyways).

Conventions

  • All public methods of a command handler class are mapped to Commands "Command Class Shortname" => "MethodName" when the method and command class shortname match. Implementing an interface for the commands is NOT required (since 1.1)
  • Domain Events are applied to Event Handlers "Event Class Shortname" => "onEventClassShortname". Only if this matches is an event listener registered.
  • Domain Events are applied on Entities/Aggregate Roots "Event Class Shortname" => "applyEventClassShortname"
  • You can optionally extend the DefaultDomainEvent which has a constructor that maps its array input to properties and throws an exception if an unknown property is passed.
  • There is also a DefaultCommand with the same semantics as DefaultDomainEvent. Extending this is not required.

Examples:

  • HelloWorld\GreetingCommand maps to the greeting(GreetingCommand $command) method on the registered handler.
  • HelloWorld\Commands\Greeting maps to the greeting(Greeting $command) method on the registered handler.
  • HelloWorld\GreetedEvent is passed to all event handlers that have a method onGreeted(GreetedEvent $event).
  • HelloWorld\Events\Greeted is passed to all event handlers that have a method onGreeted(Greeted $event).
  • HelloWorld\GreetedEvent is delegated to applyGreeted($event) when created on the aggregate root

Installation & Requirements

Use the 1.1 branch, as the dev-master is currently in heavy refactoring.

The core library has no dependencies on other libraries. Plugins have dependencies on their specific libraries.

Install with Composer:

{
    "require": {
        "beberlei/lite-cqrs": "1.1"
    }
}

Workflow

These are the steps that a command regularly takes through the LiteCQRS stack during execution:

  1. You push commands into a CommandBus. Commands are simple objects extending Command created by you.
  2. The CommandBus checks for a handler that can execute your command. Every command has exactly one handler.
  3. The command handler changes state of the domain model. It does that by creating events (that represent state change) and passing them to the AggregateRoot::apply() or DomainEventProvider::raise() method of your domain objects.
  4. When the command is completed, the command bus will check all objects in the identity map for events.
  5. All found events will be passed to the EventMessageBus#publish() method.
  6. The EventMessageBus dispatches all events to observing event handlers.
  7. Event Handlers can create new commands again using the CommandBus.

Command and Event handler execution can be wrapped in handlers that manage transactions. Event handling is always triggered outside of any command transaction. If the command fails with any exception all events created by the command are forgotten/ignored. No event handlers will be triggered in this case.

In the case of InMemory CommandBus and EventMessageBus LiteCQRS makes sure that the execution of command and event handlers is never nested, but in sequential linearized order. This prevents independent transactions for each command from affecting each other.

Examples

See examples/ for some examples:

  1. example1.php shows usage of the Command- and EventMessageBus with one domain object
  2. example2_event.php shows direct usage of the EventMessageBus inside a command
  3. example3_sequential_commands.php demonstrates how commands are processed sequentially.
  4. tictactoe.php implements a tic tac toe game with CQRS.
  5. SymfonyExample.md shows example1.php implemented within the scope of a Symfony2 project.

Setup

  1. In Memory Command Handlers, no event publishing/observing
<?php
$userService = new UserService();

$commandBus = new DirectCommandBus()
$commandBus->register('MyApp\ChangeEmailCommand', $userService);
  1. In Memory Commands and Events Handlers

This uses LiteCQRS\EventProviderInterface instances to trigger domain events.

<?php
// 1. Setup the Library with InMemory Handlers
$messageBus = new InMemoryEventMessageBus();
$identityMap = new SimpleIdentityMap();
$queue = new EventProviderQueue($identityMap);
$commandBus = new DirectCommandBus(array(
    new EventMessageHandlerFactory($messageBus, $queue)
));

// 2. Register a command service and an event handler
$userService = new UserService($identityMap);
$commandBus->register('MyApp\ChangeEmailCommand', $userService);

$someEventHandler = new MyEventHandler();
$messageBus->register($someEventHandler);
  1. In Memory Commands + Custom Event Queue

LiteCQRS knows about triggered events by asking LiteCQRS\Bus\EventQueue. Provide your own implementation to be independent of your domain objects having to implement EventProviderInterface.

<?php
$messageBus = new InMemoryEventMessageBus();
$queue = new MyCustomEventQueue();

$commandBus = new DirectCommandBus(array(
    new EventMessageHandlerFactory($messageBus, $queue)
));

Usage

To implement a Use Case of your application

  1. Create a command object that receives all the necessary input values. Use public properties and extend LiteCQRS\DefaultCommand to simplify.
  2. Add a new method with the name of the command to any of your services (command handler)
  3. Register the command handler to handle the given command on the CommandBus.
  4. Have your entities implement LiteCQRS\AggregateRoot or LiteCQRS\DomainEventProvider
  5. Use protected method raise(DomainEvent $event) or apply(DomainEvent $event)`` to attach events to your aggregate root objects.

That is all there is for simple use-cases.

If your command triggers events that listeners check for, you should:

  1. Create a domain specific event class. Use public properties to simplify.
  2. Create a event handler(s) or add method(s) to existing event handler(s).

While it seems "complicated" to create commands and events for every use-case. These objects are really dumb and only contain public properties. Using your IDE or editor functionality you can easily generate them in no time. In turn, they will make your code very explicit.

Difference between apply() and raise()

There are two ways to publish events to the outside world.

  • DomainEventProvider#raise(DomainEvent $event) is the simple one, it emits an event and does nothing more.
  • AggregateRoot#apply(DomainEvent $event) requires you to add a method apply$eventName($event) that can be used to replay events on objects. This is used to replay an object from events.

If you don't use event sourcing then you are fine just using raise() and ignoring apply() altogether.

Failing Events

The EventMessageBus prevents exceptions from bubbling up. To allow some debugging of failed event handler execution there is a special event "EventExecutionFailed" that you can listen to. You will get passed an instance of LiteCQRS\Bus\EventExecutionFailed with properties $exception, $service and $event to allow analysing failures in your application.

Extension Points

You should implement your own CommandBus or extend the existing to wire the whole process together exactly as you need it to work.

Plugins

Symfony

Inside symfony you can use LiteCQRS by registering services with lite_cqrs.command_handler or the lite_cqrs.event_handler tag. These services are then autodiscovered for commands and events.

Command- and Event-Handlers are lazily loaded from the Symfony Dependency Injection Container.

To enable the bundle put the following in your Kernel:

new \LiteCQRS\Plugin\SymfonyBundle\LiteCQRSBundle(),

You can enable/disable the bundle by adding the following to your config.yml:

lite_cqrs: ~

Please refer to the SymfonyExample.md document for a full demonstration of using LiteCQRS from within a Symfony2 project.

Monolog

A plugin that logs the execution of every command and handler using Monolog. It includes the type and name of the message, its parameters as json and if its execution succeeded or failed.

The Monolog integration into Symfony registers a specific channel lite_cqrs which you can configure differently from the default channels in Symfony. See the Symfony cookbook for more information.

Comments
  • Is JMS Serializer dependency really needed in AggregateRoot ?

    Is JMS Serializer dependency really needed in AggregateRoot ?

    Hi,

    We are building something around CQRS and instead of reinventing the wheel, we'd like to make use of your library, which is really smart.

    But actually we still have to extend your AggregateRoot base class just to get rid of the JMS Serializer dependency.

    Is this dependency really needed here ? I think this is a little bit intrusive.

    And if I refer to your comment into the code, I'd dare to bet that you share the thought ;-)

    opened by benjamindulau 8
  • Add basic example in a real web application development context

    Add basic example in a real web application development context

    When we first look at DDD in general and then give a try with a first implementation, we are immediatly stuck (well at least I was :p) at the point where since commands don't return any data, we don't know how to inform our user about what's happening.

    This is even more confusing that we can read about anything and everything on the subject.

    So, what do you think about providing an implementation example in a context of web application development ?

    It could be something really common, like user registration:

    <?php
    
    namespace Acme\UserModule\Web\Controller;
    
    class RegistrationController extends Controller
    {
        public function registerAction(Request $request)
        {
            if ('POST' == $request->getMethod()) {            
                $this->getCommandBus()->handle(new RegisterCommand(
                    $request->get('username'),
                    $request->get('email'),
                    $request->get('password')
                ));
    
                /*
                 * Since commands don't return any data,
                 * how to send messages back to the user ?
                 * Such as success or failure messages ?
                 * We'd like for instance to automatically log the user in
                 * and then redirect him to a specific page...
                 * 
                 * Should we use a synchronous command bus and 
                 * register an event handler that would do this ?
                 * 
                 * Or, should we query immediatly the model to verify its existence ?
                 */
    
                 // return $this->redirect('.....');
            }
    
            return $this->render(....); // render form template
        }
    }
    
    opened by benjamindulau 8
  • Add missing apply() method to Crud trait classes

    Add missing apply() method to Crud trait classes

    The methods in the Plugin/CRUD/Crud* trait classes call out to an apply method (which in turn calls the trait's applyResource* methods), which does not exist in the trait class definition.

    This means that the CRUD plugin cannot be used with entity objects that extend DomainEventProvider, as that class doesn't provide an apply method. Instead, Aggreagte Resource would need to be extended, which extends AggregateRoot, which means the entity would no longer have access to raise() events.

    Since the Crud traits are near duplicate methods to versions in CRUD/AggregateResource and AggregateRoot anyway, there's no point in using the traits, when you could just extend AggregateResource instead.

    But for when AggregateResource cannot be the extended class, the traits are needed, therefore the entire call chain needs to be available, so that we don't violate DRY by re-implementing apply() in every Entity object that extends DomainEventProvider but needs the create/update/delete crud traits

    opened by mbadolato 3
  • Tests fixes

    Tests fixes

    Fixes some tests where the expected inst the real thing. Also add a mock to doctrine.dbal.default_connection as the symfony plugin depends on. Only test that is failing locally is couchdb cant connect (i dont have couch).

    opened by henrikbjorn 3
  • Update Silex Plugin to work with master

    Update Silex Plugin to work with master

    Also change lite_cqrs.event_message_bus to event_message bus. This is done because it is a main entry point for LiteCQRS just like the command_bus is.

    opened by henrikbjorn 2
  • Raise event from aggregate root

    Raise event from aggregate root

    Entity objects must either extend AggregateRoot or DomainEventProvider.
    Since the usage of Doctrine and/or CRUD plugins requires the use of
    AggregateRoot, there is no ability to call raise() to execute a
    DomainObjectChanged notice. which means that we cannot have event
    handlers listening for DomainObjectChanged.
    
    Using apply() does not allow us to send DomainObjectChanged objects
    since it would subsequently then require an applyDomainObjectChanged
    method, which is unneeded.
    
    apply() and raise() do the exact same thing other than the apply()
    method calling $this->executeEvent($event), thus it's a simple matter
    to add raise() to AggregateRoot, which then enables us to do
    
    $this->raise(new DomainObjectChanged("ChangeEmail", array("email" => $email, "oldEmail" => $this->email)));
    
    from our Entity objects which extend AggregateRoot so that they can
    use the Doctrine and CRUD plugins.
    
    opened by mbadolato 1
  • Refactoring away from Base Classes and interfaces

    Refactoring away from Base Classes and interfaces

    Currently Commands and Events need to fullfil an interface, additionally, raising events is coupled to aggregate root interface instead of left for the domain to implement.

    These 3 points are necessary to make a domain completly independent from LiteCQRS, which i think is a very nice achievement in terms of dependency management.

    enhancement 
    opened by beberlei 1
  • Add MessageHeader

    Add MessageHeader

    Every event (and command?) gets a message header

    class EventMessageHeader
    {
        private $aggregateRootId;
        private $aggregateRootType;
        private $date;
        private $id;
        private $commandId;
        private $sessionId;
    }
    

    We need to start generating UUIDs for events and commands and then inside the CommandBus and EventMessageBus set all this parameters automatically.

    opened by beberlei 1
  • [PLUGIN] [SYMFONY] Unable to find command_bus service

    [PLUGIN] [SYMFONY] Unable to find command_bus service

    h5. Description

    Unable to find command_bus service because handler register after container builder remove unused service.

    https://github.com/beberlei/litecqrs-php/blob/master/src/LiteCQRS/Plugin/SymfonyBundle/LiteCQRSBundle.php#L19

    $container->addCompilerPass(new HandlerPass(), PassConfig::TYPE_AFTER_REMOVING);
    
    In ContainerBuilder.php line 1013:
                                                                                 
     [Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException]  
     You have requested a non-existent service "command_bus".                    
                                                                                 
    
    Exception trace:
    () at /project_dir/vendor/symfony/dependency-injection/ContainerBuilder.php:1013
    Symfony\Component\DependencyInjection\ContainerBuilder->getDefinition() at /project_dir/vendor/symfony/dependency-injection/ContainerBuilder.php:1049
    Symfony\Component\DependencyInjection\ContainerBuilder->findDefinition() at /project_dir/vendor/beberlei/lite-cqrs/src/LiteCQRS/Plugin/SymfonyBundle/DependencyInjection/Compiler/HandlerPass.php:13
    LiteCQRS\Plugin\SymfonyBundle\DependencyInjection\Compiler\HandlerPass->process() at /project_dir/vendor/symfony/dependency-injection/Compiler/Compiler.php:95
    Symfony\Component\DependencyInjection\Compiler\Compiler->compile() at /project_dir/vendor/symfony/dependency-injection/ContainerBuilder.php:748
    Symfony\Component\DependencyInjection\ContainerBuilder->compile() at /project_dir/vendor/symfony/http-kernel/Kernel.php:544
    Symfony\Component\HttpKernel\Kernel->initializeContainer() at /project_dir/vendor/symfony/http-kernel/Kernel.php:133
    Symfony\Component\HttpKernel\Kernel->boot() at /project_dir/vendor/symfony/framework-bundle/Console/Application.php:65
    Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /project_dir/vendor/symfony/console/Application.php:145
    Symfony\Component\Console\Application->run() at /project_dir/bin/console:40
    

    h5. Configuration used

    lite_cqrs:
      monolog: true
    

    I will propose a correction, I will leave you the pleasure of the returns

    opened by jean-pasqualini 0
  • Store old events to apply them again on commit

    Store old events to apply them again on commit

    Hi !

    I give a try to your lib and it looks pretty awesome with the last changes.

    I just found a problem. You apparently does not keep old events. But you are looking for them here: https://github.com/beberlei/litecqrs-php/blob/master/src/LiteCQRS/EventStore/OptimisticLocking/OptimisticLockingEventStore.php#L67

    So I just add a line to store them.

    With this fix I succeeded to make all your stuff working.

    opened by tyx 0
  • Implement DomainEventProviderInterface, deprecate AggregateRepositoryInterface

    Implement DomainEventProviderInterface, deprecate AggregateRepositoryInterface

    Based on my discussion with @beberlei, implement a DomainEventProviderInterface which uses EventProviderInterface in the type hints and deprecate AggregateRepositoryInterface in place of the new interface

    opened by mbadolato 0
  • Command execution blocking sequential execution in sync env

    Command execution blocking sequential execution in sync env

    Hey,

    I'm taking a look at the SequentialCommandBus and I don't understand how it can work if a command is added to stack during the execution context of another command in a synchronous system.

    Let's say I want to implement some sort of a ProcessManager. This process manager would listen to some domain events and will send new commands to the CommandBus.

    This entire process is initiated by a first command execution. Given than the command bus blocks the execution until a command handling has ended, the underlaying commands (send synchronously) won't be executed. They will stack in the command bus and will be lost when the request dies.

    Where is the DirectCommandBus introduced in this example: https://github.com/beberlei/litecqrs-php/blob/master/example/example3_sequential_commands.php ?

    Cheers

    opened by benjamindulau 2
  • Question : How to get Entity property?

    Question : How to get Entity property?

    I am discovering the CQRS beauty and I am trying to figure out how to use it in a Symfony2 project (the do CQRS process in the Controller). Based on the example, how should I do to retreive the User profile ? Is it good practice to create a getEmail method for instance or should I use the repository to get scalar data or is there another way you guys suggest ?

    opened by fpoirier1 0
  • LiteCQRS incompatible with JMS Serilizer > 0.9

    LiteCQRS incompatible with JMS Serilizer > 0.9

    JMS changed things between 0.9 and 0.10. The more minor of the changes is the change from \SerializerBundle to \Serializer in the paths for Annotations etc. Not a big deal.

    The big deal, however, is the removal of of SerializationHandlerInterface and DeserializationHandlerInterface. I am not familiar enough (yet) with LiteCQRS's internals to know how the handlers need to be tweaked.

    Hopefully this is something that could be rectified/implemented quickly by @beberlei, as we're trying to downgrade to Serialize 0.9 in our project but it look like that is breaking other things by doing that.

    [edit: Yep, downgrade totally hosed things for us, so 0.9 isn't option. Need to temporatily work around this by not using JMS Serializer in my objects where LiteCQRS is used otherwise we get a path issue when LCQRS tries to look for JMS\SerializerBundle\Annotation since that's now incorrect]

    Details in the JMS Serializer Bundle Upgrading doc

    opened by mbadolato 0
  • Asynchronous CommandBus

    Asynchronous CommandBus

    I see that the EventMessageBus is build to support Asynchronous execution by default (maybe a sideeffect of pushing events to EventStore) but Commands do not have that by default mainly because a SerializerInterface is missing for those.

    Any ideas on how this could easily be added? Maybe by generalizing the SerializerInterface?

    opened by henrikbjorn 0
Owner
Benjamin Eberlei
Founder of @tideways, PHP performance monitoring, profiling and exception tracking software. @doctrine core member and occasional @php contributor
Benjamin Eberlei
Orkestra is a library of infrastructure and architecture helpers for creating CQRS applications

Orkestra Orkestra is an opinionated framework with a plethora of recommendations on architectural design that we use internally at Morebec to develop

Morébec 2 Aug 18, 2021
PHP Lightweight Message Bus supporting CQRS.

Prooph Service Bus PHP 7.1+ lightweight message bus supporting CQRS and Micro Services Important This library will receive support until December 31,

null 440 Nov 20, 2022
🐘 🎯 Hexagonal Architecture, DDD & CQRS in PHP

?? ?? Hexagonal Architecture, DDD & CQRS in PHP Example of a PHP application using Domain-Driven Design (DDD) and Command Query Responsibility Segrega

CodelyTV 2.5k Jan 6, 2023
Ecotone Framework is Service Bus Implementation. It enables message driven architecture and DDD, CQRS, Event Sourcing PHP

This is Read Only Repository To contribute make use of Ecotone-Dev repository. Ecotone is Service Bus Implementation, which enables message driven arc

EcotoneFramework 308 Dec 29, 2022
Small library providing some functional programming tools for PHP, based on Rambda

Functional library for PHP. Features: set of useful functions helpful in functional programming all functions are automatically curried every array ca

Wojciech Nawalaniec 5 Jun 16, 2022
Enraged Xenomorph - DDD/CQRS Symfony Application Boilerplate

Enraged Xenomorph - DDD/CQRS Symfony Application Boilerplate This project is very opinionated attempt to compile a bit of experience, few good practic

Gniewomir Świechowski 1 Jan 10, 2022
Dockerise Symfony Application (Symfony 6 + Clean Architecture+ DDD+ CQRS + Docker + Xdebug + PHPUnit + Doctrine ORM + JWT Auth + Static analysis)

Symfony Dockerise Symfony Application Install Docker Install Docker Compose Docker PHP & Nginx Create Symfony Application Debugging Install Xdebug Con

null 48 Jan 5, 2023
A Symfony project made with DDD, CQRS and Hexagonal Architecture

Symfony Blog DDD + CQRS + Hexagonal Architecture A Symfony blog project made with CQRS, Hexagonal Architecture and DDD Docker integration This project

null 5 Aug 10, 2022
Because every Wedding RSVP website needs to follow DDD, CQRS, Hexagonal Architecture, Event Sourcing, and be deployed on Lambda.

Our Wedding Website Because every Wedding RSVP website needs to follow DDD, CQRS, Hexagonal Architecture, Event Sourcing, and be deployed on Lambda. ?

Edd Mann 3 Aug 21, 2022
Clean Architecture, DDD and CQRS using Symfony 6

Task manager system using Clean Architecture, DDD and CQRS. Environment setup Install Docker Clone the project: git clone https://github.com/k0t9i/Tas

null 3 Sep 5, 2022
A small library for validating International Bankaccount Numbers (IBANs) based on the IBAN Registry provided by SWIFT

A small library for validating International Bankaccount Numbers (IBANs) based on the IBAN Registry provided by SWIFT

Jan Schädlich 69 Dec 18, 2022
Lavarel-based school management platform for small and medium institutions

About this project Academico is an open-source, Lavarel-based school management platform. Its main features include course management, enrolments mana

Academico 160 Dec 24, 2022
JsonCollectionParser - Event-based parser for large JSON collections (consumes small amount of memory)

Event-based parser for large JSON collections (consumes small amount of memory). Built on top of JSON Streaming Parser This packa

Max Grigorian 113 Dec 6, 2022
A small, modern, PSR-7 compatible PSR-17 and PSR-18 network library for PHP, inspired by Go's net package.

Net A small, modern, PSR-7 compatible PSR-17 and PSR-18 network library for PHP, inspired by Go's net package. Features: No hard dependencies; Favours

Minibase 16 Jun 7, 2022
A small, modern, PSR-7 compatible PSR-17 and PSR-18 network library for PHP, inspired by Go's net package.

Net A small, modern, PSR-7 compatible PSR-17 and PSR-18 network library for PHP, inspired by Go's net package. Features: No hard dependencies; Favours

Minibase 16 Jun 7, 2022
A small library to help run PHP servers easily and quickly.

PHP Server A small library to help run PHP servers easily and quickly. Installation composer require ahmard/php-server Usage PHP Built-In Server An i

Ahmad Mustapha 9 Dec 31, 2022
A small PHP library to generate YouTube-like ids from numbers.

A small PHP library to generate YouTube-like ids from numbers. Use it when you don't want to expose your database ids to the user.

Vincent Klaiber 4.9k Dec 30, 2022
Small Library to Serve Images in PHP in a Better Way (Resize, Compress) with Caching Support

A library for serving images and optimizing, changing their sizes, this library uses the caching feature, and in addition, it is very fast and small in size. In addition to these features, this library also optimizes images.

Developix 9 Oct 18, 2022
nUberSoft is a small in-the-works framework Library for PHP

nUberSoft is a small in-the-works framework Library for PHP. It is a mix of old and new due to it's long development timeframe. Documentation is not as robust as it should be. The majority of this framework will work for non-Linux-based systems, but it is not tested and some security (.htaccess) are not read by Win servers.

Rasclatt 0 Jan 10, 2022