Is there a good way of doing CQRS when combined with Event Sourcing?
One way I thought about was doing this in the Command handler (of a Persistent Actor) as soon as the Command was turned into an Event and persisted to the event log (these Events represent the Write model), I would send the event using the event bus to interested subscribing query actors so they can update their Query model.
The other way I was thinking (provided the journal supports it) is to use persistence queries (via Akka Streams) like allPersistenceIds
or currentPersistenceIds
and the query side (possible query actors) could perform this periodically.
Am I on the right path? Is there a better way of doing it?
I think the approach you have mentioned first will work without problems. I would not say that for the second one. If I understand you correctly you would like to have your query projection to pull for updates instead of being pushed via an event bus. I think the problem here is that you have to distinguish between events you have already processed and such events which are new (updates). I am not sure if the Akka EventStore journal can deal with that but I would doubt that.
After doing some research and acknowledging the fact that updates to the read side can fail during the handling of the command (first approach above) which would involve dealing with rolling back and transaction. A better approach is to make use of Persistence Query. This relies heavily on your Event Journal supporting the different features of Persistence Query (like AllPersistenceIdsQuery
and EventsByTag
) which are currently supported by journals like Cassandra, Event Store and some others.
The idea is to decouple the Command Side from the Query Side(s) wherein the Command Side need not know there are any Query Side(s) at all. This decoupling is given to us using Persistence Query. The idea is that the Command Side should only be concerned with validating incoming Commands and persisting Events into the Event Journal. That's it, no awareness of the Query Side.
Now for the Query Side(s), you use Persistence Query like EventsByTag
or AllPersistenceIdsQuery
to get a Source[Event]
from the journal which is a back-pressured live stream of Event
s from the Event Journal and you use this abstraction to feed your Read Sides. You can find approaches here
Let's think about failures
What if the Command Side goes down?
no new Event
s will be persisted to the Event Journal, the Read Side Persistence Queries will happily chug along producing no new Event
s in the stream. When the Command Side comes back up, everything will resume and now the Read Side Persistence Queries will see new Event
s in the stream.
What if one of the Read Sides go down?
Not a big problem, you would wipe the read side database and restart that Persistence Query from the beginning and away we go. We can definitely improve this by using the concept of Resumable Projections. The idea is to keep storing the current offset of each Event
you read so that if your Read Side goes down, we can simply resume from the point that we were reading instead of having to start all the way from scratch. A word of advice, if you need Effectively-once delivery then you might want to look into using idempotency filters to avoid duplicates. If you do this, you can do some optimizations where you don't need to persist every ID, but just at certain intervals.
Some other considerations
What if you need to introduce new Read Sides and they depend on Command Side Events from inception?
This decoupled approach allows you to do this. The idea is that you don't delete events from the Event Store, simply fire up yet another Persistence Query and have it feed your new read side and don't forget to use Resumable Projections if you need to keep up to date.
These approaches have the implicitly implied having Persistence Query and the logic to feed the Read Side(s) and the Resumable Projection on the same system.
Another approach is to couple Persistence Query with Akka HTTP and expose a streaming endpoint that exposes your Event Journal and allows you to either get all Events/certain Events from inception or from a certain offset. This way allows you to do some further decoupling but if you use this approach, you really want to have Resumable Projections since the failure is now increased due to introducing HTTP. Now your Read Sides can consume this streaming endpoint and have the Resumable Projection on their side and new Read Sides can be introduced in a more decoupled manner.
These are just some of my learnings that I have collected since I have started working with Akka. Perhaps some more experienced people have better approaches to CQRS.
If you are looking for code samples, I highly recommend taking a look at Mastering Akka by Christian Baxter which describes the approach in much greater detail and also this post.
Thanks for reading