I try to get a better understanding of the corresponding roles of CQRS and EventSourcing regarding persistence especially when both patterns will be combined.
Let's say, we have a CreateUserCommand. The user shall:
- be stored into a relational database, which will be used as the base-db for a traditional e-commerce-system (with checkout etc.)
- be stored into a special search database (ElasticSearch)
- be imported into a CRM
- notified about its successful registration
Many blog posts and even some READMEs to corresponding CQRS+ES-Libraries write it like this:
- the command will be responsible for the main storage and thus store the user in the main database, after that it will dispatch an event
UserCreated - such dispatched event will additionally be used to write into special search-/query-/read-databases like ElasticSearch to prepare the data for any Read-Queries (the "R"-part from CQRS) and to send the user into the CRM and so on.
However, I doubt that this approach would make the EventStore an effective source of truth. Isn't it, instead, a deviation from the design pattern principles of event-sourcing? But I found so many sources in literature, where Event Sourcing is rather described only as a tool for depicting secondary data sources and not as a primary data source.
I will break down the approach mentioned above, which literature talks about more often and add "my" approach according to my personal understanding, how it should be "done right" instead:
Approach 1:
Command-Side:
CreateUserCommand
-> CreateUserCommandHandler
-> persists the User given in the Command into UserRepository (relational Main-Database)
-> dispatches UserCreated-event
Event-Side:
Event Bus:
-> Handler for UserCreatedEvent:
-> persist the User in the payload also into ElasticSearch
-> send User-registration-mails
-> add this user into an external CRM
To my understanding, at least in theory, the EventBus should be the one-and-only source-of-truth, which eventually means, that any command handler should persist nothing. Instead, they would only dispatch events doing so. According to that, I would see the persistence like this:
Approach 2:
Command-Side:
CreateUserCommand
-> CreateUserCommandHandler
-> dispatches UserCreated-event
Event-Side:
Event Bus:
-> Event Handler 1 for UserCreatedEvent:
-> persists the User in the payload into relational main-database
-> Event Handler 2 for UserCreatedEvent:
-> persists the User in the payload also into ElasticSearch
-> Event Handler 3 for UserCreatedEvent:
-> send User-registration-mails
-> Event Handler 4 for UserCreatedEvent:
-> add this user in external CRM
(... and many many more)
Which approach is right?
Not sure if there is a dogmatic "right or wrong" in this. I would make it dependent on the scope of the service accepting the data. If this service also has functions to retrieve the record or aspects of it, i would write the projection right away without running it though the event stream first. Only then we send the event. I know one could argue that it is not a by-the-book solution, but on the other hand it makes sure to answer a read directly after the write correctly. In your approach 2, you add latency to updating the service's DB which could lead to irritations later on. In Approach 1 however, reconstructing the projection from the event stream becomes a special case, because "UserCreated" events usually need no processing in the lead service. We implemented an event handler for that, which in normal operations ignores the Event but in "replay mode", processes the events to reconstruct the data from the the stream.