Giter VIP home page Giter VIP logo

litecqrs-php's Introduction

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.

litecqrs-php's People

Contributors

aderuwe avatar beberlei avatar coderbyheart avatar henrikbjorn avatar igorw avatar pborreli avatar schnipseljagd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

litecqrs-php's Issues

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
    }
}

Tag a version.

Hey Beberlei

Could you tag a version so that i dont have to use @dev or minimum-stability: dev in my composer file?

Question on access

Why does setAggregateId() in the DomainEvent public. Shouldn't it be protected or even private access level. To keep the Event immutable.

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?

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

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 ?

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.

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 ;-)

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

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.

[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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.