Standard for CQRS and Event sourcing

354 views
Skip to first unread message

Constantin Galbenu

unread,
May 19, 2017, 2:53:54 AM5/19/17
to PHP Framework Interoperability Group
What do you think about introducing some standards regarding CQRS and Event sourcing? 

I have a working/in production web based CRM application that uses one of my micro-frameworks . It uses the command handler style described on cqrs.nu, where command handlers are aggregate methods. The aggregate is identified by the ID (extracted from Command) and class (manually/automatically subscribed). 

The normal flow is this (the flow implemented by my DefaultCommandDispatcher):
- the client code (i.e. a REST endpoint) creates a new Command and it sends the command to the CommandDispatcher
- the CommandDispatcher identifies the aggregate class (using CommandSubscriber)  and ID (using Command::getAggregateId) then creates a new aggregate instance
- it rehydrates the aggregate from the event store (by loading all the prior events and applying them to the aggregate instance)
- it calls the aggregate's command method (identified by using CommandSubscriber) and collects the yielded events (also it applies them onto the aggregate itself, one by one, at collect time - a kind of reactive aggregate); 
- it persists the collected events to the EventStore, FutureEventsStore or ScheduledCommandStore depending on the yielded message type
- it notifies the event handlers using EventDispatcher
- it discards the aggregate

The aggregate should not inherit from anything and nothing should be injected into it. It should remain pure, side effect free. Any side effect should be observed by its yielded events.

I like this style because it reduces a lot of code duplication found in the other style (command handler being in the Application layer) where almost every command handler looks the same: it loads the Events from the store using a Repository, it factories a new Aggregate, it rehydrates the Aggregate (by applying the events to the Aggregate), it calls a method on the aggregate, it collects the events, it persists the events and then notify all subscribers (event handlers). In cqrs.nu's style this entire algorithm can be extracted in a CommandDispatcher.

So, I propose (at least) the following interfaces (in the namespace Cqrs)

//events

interface Event;

interface EventDispatcher;

interface EventSubscriber;

interface MetadataFactory;

interface
EventStore;

interface
AggregateEventStream extends EventStream;

interface EventStream extends \IteratorAggregate;

interface EventStreamGroupedByCommit extends EventStream;

//commands

interface
Command;

interface CommandDispatcher; //can be decorated by a CommandDispatcherWithCommandValidation

interface CommandSubscriber;

interface MetadataWrapper;

interface CommandValidatorSubscriber; //used by CommandDispatcherWithCommandValidation

//scheduling

interface FutureEventsStore

interface
ScheduledCommand extends Command, ScheduledMessage;

interface ScheduledEvent extends ScheduledMessage

interface ScheduledMessage extends IdentifiedMessage;

interface
IdentifiedMessage;

interface CommandScheduler;

interface ScheduledCommandStore;

//read models and sagas (process managers)

interface
ReadModelInterface;

interface SagaEventTrackerRepository;


I know that there are a lot of interfaces, but in fact, in a working application, more interfaces are needed.

In order for this to work, the minimal requirements from the code in the Domain layer is the Command interface (for its getAggregateId method). The Event interface should be used only to detect event handlers by reflection. So, the domain code remains (almost) pure.

I have a todo example on github if you want to learn more.

I know that my question mixes the proposed standard with my implementation but I don't know how to start the discussion otherwise.

So, what do you think?

Rivera, John

unread,
May 19, 2017, 9:38:20 AM5/19/17
to php...@googlegroups.com
Hello!

Thanks for bringing this topic up — I’m a big fan of CQRS/ES!

But I’ll begin with a caveat — I do not believe it’s suitable to a PSR. That is my opinion, and I am definitely open to debate.

Continuing with the assumption that I was convinced that it’s a worthy PSR… I think your interfaces are definitely excessive. Here’s my minimum requirements for a working CQRS application:

Interface Command {}

interface Query {}

interface Event {}

interface Mediator
{
    public function send(Command $command): void;
    public function request(Query $query): mixed;
    public function raise(Event $event): void;
}

That’s it.

You’re probably asking ‘wait a minute, what about the CommandHandler and EventHandler?” — we could have interfaces for each, as follows:

interface CommandHandler
{
   public function __invoke(Command $command): Event;
}

interface EventHandler
{
    public function __invoke(Event $event): void;
}

However, I eliminated those interfaces in my application because without them, I can write in the concrete Command object as a type hint instead of being forced to do a ‘if ($command instanceof ConcreteCommand) { … }”. Requiring the handlers to be invokable is sufficient. Perhaps someday we’ll get generics in PHP…

Another typical way of defining the Handler interfaces is to replace __invoke(…) with handle(…), but I liked the elegance of working with invokable classes in the Mediator, and that I could enforce an sort of ‘interface’ without defining one.

One more thing — requiring that the Command interface has a getAggregateId() method is a no-no — you’re assuming that we would use DDD as the domain. Don’t assume this — in fact, I’m not using DDD in my application at all, only CQRS/ES.

Your Subscriber stuff seems intriguing, however — I’ll take a closer look at your application to see how they work.

Thanks for bringing up the topic — I hope this will lead to a illuminating debate!
John

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/0c9a062b-4f1b-4f3c-85d3-433a93fdb234%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

xprt64

unread,
May 19, 2017, 10:44:20 AM5/19/17
to php...@googlegroups.com
Hi, thanks for the feedback, it's nice to see that other are thinking about CQRS in PHP :)

> ... you’re assuming that we would use DDD as the domain ..

I assumed DDD as the approach, not the "domain". The domain is the business domain and probably any (complex enough) domain would work (we are not talking about CRUD implementations, we are assuming that the problem CQRS is trying to solve is not solvable by CRUD). I assumed DDD because it is one of the nicest approaches used to solve complex domains and CQRS is one of the DDD implementations.

To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/0c9a062b-4f1b-4f3c-85d3-433a93fdb234%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/78LIU6d7mic/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Best regards,
Constantin Galbenu,
Tel: +40728247366
Skype: xprt64
Twitter: @gicagalbenu

Rivera, John

unread,
May 19, 2017, 11:07:35 AM5/19/17
to php...@googlegroups.com
Hello —

Let me give you an example.

We have a requirement: an user uploads a file. This file is to be ETL’d into the database. But before being uploaded, the file needs to be validated. And this validation needs to be user-friendly, so the user can fix the file (we use a custom file format for file uploads). If all goes well, the file is to be ETL’d into the staging database.

So I have a command that validates, and uploads, the file. It raises (actually, returns) two possible events: files uploaded, or validation failed. The validation failed event triggers the compilation of the errors into a user-friendly to be returned as a response. The file uploaded event triggers the ETL process which grabs the file, performs ETL, and loads it directly into the staging database.

Where did DDD come into play here?

John

To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

xprt64

unread,
May 19, 2017, 12:27:17 PM5/19/17
to php...@googlegroups.com
DDD has many patterns, some are at the strategic level and some are the tactical level. Aggregates are at the tactical level are are the building blocks of DDD. Aggregates ensure that the application is in consistent state at all times.

Regarding your example, DDD could come into play by defining a FileImport aggregate that yields (raises, returns, whatever; I use the super-nice yield feature of PHP) FileUploaded event or throws an FileIsInvalid exception. You could raise two events if the failed validation is an important aspect of the business that you want to capture, otherwise I would choose to just throw an exception.

Anyway, I think I get your point in the first reply: there is a need to send commands to something other than an Aggregate/Entity, like a application service, and I agree with you. So, my Command propose should in fact be a AggregateCommand that inherits from (the now simpler) Command and adds the getAggregateId method.



--
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/78LIU6d7mic/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Rivera, John

unread,
May 19, 2017, 12:31:08 PM5/19/17
to php...@googlegroups.com
Regarding an AggregateCommand extending Command, that is ironically enough exactly how I did it (albeit with a slightly different name) :)

John

To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

xprt64

unread,
May 19, 2017, 12:39:11 PM5/19/17
to php...@googlegroups.com
So, there is hope to standardize this :)

This would be good for the PHP community as the efforts would be split in implementing the main components of the architecture like the EventStore and the CommandDispatcher.

The EventStore that I use is done in MongoDB but some greater minds than me (and there are many, that is the power of the community) could implement something that is faster, more scalable, more robust, more tested, more whatever.
 
The CommandDispatcher could be implemented as a RabbitMQ queue and many decorators implemented (like validators, pipe middlewares etc).

--
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/78LIU6d7mic/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Stefano Torresi

unread,
May 19, 2017, 1:29:38 PM5/19/17
to php...@googlegroups.com
Hey folks,

While I find this an interesting discussion, may I remind you that we try to keep a soft limit of maximum two messages per thread per day, to avoid flooding list subscribers with too many emails.

If you need a quicker pace of discussion, consider changing medium to IRC or Slack.

Thanks

To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/0c9a062b-4f1b-4f3c-85d3-433a93fdb234%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/78LIU6d7mic/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.



--
Best regards,
Constantin Galbenu,
Tel: +40728247366
Skype: xprt64
Twitter: @gicagalbenu

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

--
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/78LIU6d7mic/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.



--
Best regards,
Constantin Galbenu,
Tel: +40728247366
Skype: xprt64
Twitter: @gicagalbenu

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

--
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/78LIU6d7mic/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

--
Best regards,
Constantin Galbenu,
Tel: +40728247366
Skype: xprt64
Twitter: @gicagalbenu

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Marco Perone

unread,
May 20, 2017, 3:58:00 AM5/20/17
to PHP Framework Interoperability Group
If I may add something to the discussion, I believe that the whole ES/CQRS process is too big for a single PSR. You could definitely use CQRS without ES and, theoreticallym you could even do ES without CQRS. Moreover, several components, like the command bus and the event bus, could be used on their own on other kinds of applications. So an approach to this would be to identify which are the key components and isolate them.

Another last thing, I would suggest to have a look at the existing ES/CQRS frameworks like Prooph or Broadway and check what do they have in common.

kon...@codeliner.ws

unread,
May 23, 2017, 7:15:49 AM5/23/17
to PHP Framework Interoperability Group
Hi,

what you do is defining a CQRS/ES framework not a PSR. Looking at the listed interfaces we would need to change a lot of stuff in prooph to support such a PSR so we likely won't support it.

Marco Perone has provided a good summary. CQRS, Event Sourcing, Event Store, Read Models, Projections and Messaging should be treated as independent building blocks (what we do at prooph see: http://getprooph.org/#features ) 

Every piece of the puzzle deals with messages in one way or the other so a Message PSR like discussed in this thread: https://groups.google.com/forum/#!msg/php-fig/CcFN7zr90Wo/3ihpslRDBwAJ

would be a good starting point. Everything else could be discussed later but I'm not sure if we really need a PSR for command bus/dispatcher or an event store. 

Best regards,
Alex

Larry Garfield

unread,
May 23, 2017, 1:24:10 PM5/23/17
to php...@googlegroups.com
*Puts on CC member hat*

While ES/CQRS is cool, it doesn't seem like something that's PSR-appropiate at this point.

1) I don't see a great deal of *interoperability* value to standardizing a CQRS structure.  It doesn't seem to help make it easier for frameworks and libraries to swap pieces out.  I can't imagine a CQRS system being something you'd swap out the way you would a logger, for instance.

2) As noted, ES/CQRS takes in a ton of territory; while individual pieces of it MAY make sense to standardize, CQRS itself is too big a problem space for it to make sense.

3) Any such PSR should come with buy-in from at least some of the major CQRS implementations present today, which seems missing.

4) It's still a new-enough problem space for PHP that there's still plenty of experimentation and evolution to be done.  A PSR would likely hinder that in this case rather than encourage it.

--Larry Garfield
Reply all
Reply to author
Forward
0 new messages