For a small microservice we’re building, we’ve decided to use event sourcing and have chosen EventStore for the write model. We are now trying to decide what to use for the read model. A few details about the application:
-
There is only 1 aggregate root object (representing the bounded context of the app’s domain).
-
We expect each stream to have only a few events in it – probably less than 5, most likely less than 10, definitely less than 20.
-
Each time we query to read a piece of data on the aggregate, we will always know the ID of the aggregate (and thus can deduce a stream name at read time).
Given this, it seems that we have at least 3 alternatives for how to implement a read model in this system:
1.) Stand up a separate document database instance like RavenDB in the app, and use domain event handlers to keep its state eventually consistent with the read model.
2.) Make the write-time aggregate root double as a read model by exposing public data properties on it.
- Make a separate read model object that is like the aggregate root but has no behavior methods, and instead exposes public data access properties. Load events into this read object (either directly from the write streams, or from separate streams generated by a user projection) and replay them to hydrate read model state.
It was my initial inclination to go with alternative #1. One of my team members has objected to this due to the additional overhead associated with it, and has suggested alternative #2. After initially arguing against that, I’ve put some thought into it and he does have some valid points (i.e. we will always know the aggregate root id, and there are not very many events to replay, so it should be fast). It still feels wrong though to expose public properties on the write model, since that is where the behavioral methods should be – I don’t want a read client to have access to those. (One solution to that problem could be to mark the behavior methods as internal, so that only command handlers in the same assembly can access them, which still feels wrong.)
Before I propose alternative #3, I wanted to pass it by this group to ask for opinions. Is alternative #3 a viable one? Are there any other alternatives I am not considering? It is wrong for the read model to hydrate from the same streams that the aggregate writes to, and if so, why? (For some reason I could’ve sworn I read somewhere to be cautious about running user projections in a production system, but I can’t remember where, and now I am wondering whether I imagined it.) Keep in mind this is a rather small service-to-service application that has no user interface and is currently not very complex. Most of the application’s complexity comes from our goals to make it continuously deliverable to production, and to compensate for cascading downstream failures when other external services it depends on go down.
Thanks,
- Dan