Download the PHP package neos/cqrs without Composer
On this page you can find all versions of the php package neos/cqrs. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Package cqrs
Short Description Lean and opinionated way to integrate Event Sourcing and CQRS pattern in your Flow framework package
License MIT
Informations about the package cqrs
Event Sourcing and CQRS for Flow Framework and Symfony
Library providing interfaces and implementations for event-sourced applications.
- For Symfony, use Neos.EventSourcingSymfonyBridge which adapts this package to the Symfony World.
- For Neos/Flow, use this package directly.
Getting started
Install this package via composer:
Setting up an Event Store
Since there could be multiple Event Stores simultaneously in one application, this package no longer comes with a pre-configured "default" store. It is just a matter of a couple of lines of YAML to configure a custom store:
Configuration/Settings.yaml:
This registers an Event Store, identified as "Some.Package:EventStore"1, that uses the provided Doctrine storage adapter that persists events in a database table2.
To make use of the newly configured Event Store one more step is required in order to finish the setup (in this case to create the corresponding database table):
:information_source: Note...
> By default, the Event Store persists events in the same database that is used for Flow persistence. > But because that can be configured otherwise, this table is not generated via Doctrine migrations. > If your application relies on the events table to exist, you can of course still add a Doctrine migration for it.Obtaining the Event Store
To get hold of an instance of a specific Event Store the EventStoreFactory
can be used:
:information_source: Alternative ways...
Alternatively you can inject the Event Store directly: But in this case you have to specify _which_ event store to be injected. This can be done easily using Flow's [Object Framework](https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/ObjectManagement.html#object-framework): *Configuration/Objects.yaml:* If you use Flow 6.2 or newer, you can make use of the [virtual object configuration](https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/ObjectManagement.html#virtual-objects) to simplify the process: *Configuration/Objects.yaml:* And, finally, if you happen to use the event store from many classes, you could of course create a custom Event Store facade like: *Classes/CustomEventStore.php* and inject that.Writing events
Example event: SomethingHasHappened.php
Reading events
Reacting to events with Event Listeners / Projectors
A Projector is a special Event Listener which does not have side-effects (besides updating the projection), and can thus be reset and replayed.
In order to react upon new events you'll need an Event Listener:
The when*()
methods of classes implementing the EventListenerInterface
will be invoked whenever a corresponding event is committed to the Event Store.
Since it is possible to have multiple Event Stores the listener has to be registered with the corresponding Store:
Configuration/Settings.yaml:
This registers the Some\Package\SomeEventListener
so that it is updated whenever a corresponding event was committed to the "Some.Package:EventStore".
To register all/multiple listeners with an Event Store, you can use regular expressions, too:
Configuration/Settings.yaml:
Keep in mind though that a listener can only ever by registered with a single Event Store (otherwise you'll get an exception at "compile time").
In case you implement a projector, you should implement ProjectorInterface
.
Triggering Side-Effects after Projections have been updated
Sometimes, it is necessary to refresh dependent data after a certain projection has been updated.
WARNING: If possible, first try hard to build a second, independent projection. Refreshing state after a projection has updated is something like a "dependent projection" which only makes sense if the same data of the projection is stored in another representation (e.g. a data warehouse, or a search index).
This can be implemented in two ways:
Variant 1: implement AfterInvokeInterface
in your Projector, and directly trigger an external action.
The afterInvoke
method is triggered for every event, thus there is no batching or anything like that. This is
fine in simpler scenarios, but not if you have loads of events which always lead to similar refresh actions.
Variant 2: implement AfterCatchUpInterface
The afterCatchUp
method is triggered at the end of a projector update run, and can be used to fire off a batch update
to an external system.
In case you want to implement chunking (i.e. trigger an update of the external system every e.g. 100 events),
you can do that by implementing both AfterInvokeInterface
and AfterCatchUpInterface
: In afterInvoke
, you would
see if the chunk size was reached (and if yes, trigger the external call and reset your tracking state).
In afterCatchUp
you would trigger the remaining calls for the unfinished batch at the end.
Reacting to Events Synchronously (i.e. Projection Update Synchronously)
When embracing asynchronicity, you establish a scaling point where the application can be "torn apart":
- Application can be more easily scaled on this point (projections can update asynchronously)
- different projections can update in parallel
- For long running actions, the system behaves non-blocking: You do not need to wait until you can respond to the client.
On the other hand, asynchronicity introduces complexity, that will leak to many other application parts. Usually, the frontend then needs to implement optimistic updates and failure handling.
WARNING: You will give up one of the main performance advantages of Event Sourcing. Think twice before doing this, and think through your assumptions of the system, because we all have a tendency to prefer the "simple, synchronous world".
For smaller amounts of moving data, where you won't run into performance problems due to synchronous execution, it is sometimes useful to move back to a "synchronous" mode, where the projections are DIRECTLY updated after the events are stored.
How can we force a projection (or another event listener) to run synchronously?
You can call the Neos\EventSourcing\EventListener\EventListenerInvoker::catchup()
method directly - this then calls
the projectors (and other event listeners as needed).
Best is if you create a service which contains the following snippet for each projector you want to update synchronously:
Event Sourced Aggregate
The neos/event-sourcing
package comes with a base class that can be used to implement Event-Sourced Aggregates.
Aggregate Construction
The AbstractEventSourcedAggregateRoot
class has a private constructor. To create a fresh aggregate instance you should define a named constructor:
Aggregate Repository
This Framework does not provide an abstract Repository class for Aggregates, because an implementation is just a couple of lines of code and there is no useful abstraction that can be extracted. The Repository is just a slim wrapper around the EventStore and the Aggregate class:
Tutorial
See Tutorial.md
Glossary
See Glossary.md
1: The Event Store identifier is arbitrary, but it's good practice prefixing it with a package key in order to prevent naming clashes ↩
2: The Doctrine Event storage uses the same database connection that is configured for Flow and persists events in a table neos_eventsourcing_eventstore_events
by default – this can be adjusted, see ↩
All versions of cqrs with dependencies
neos/flow Version ^7.0 || ^8.0 || ^9.0 || dev-master
flowpack/jobqueue-common Version ^3.0 || dev-master
symfony/serializer Version ^5.1
symfony/property-access Version ^5.1
ramsey/uuid Version ^3.9 || ^4.0