How do read models work in event sourcing?
My current understanding is as follows:
Command gets executed, triggering multiple events
Events are propagated to event store
Events are published to subscribers, including read model stores
Read model stores maintain current read model by handling subscribed events
These read models are then used in any querying done in application
However I don’t understand the following:
How do read model stores handle a server restart? Since they currently only populate the read model when events are published to them, surely when the server restarts no events will be sent so they can’t initialise the correct read model? Only things I can think to solve this are:
Storing read models in a database – but then it doesn’t sound like a read model anymore, and now I am storing (and paying for storage of) both write models (events) and read models. Also sounds like a lot of duplicated data.
Read model stores initialise their read models on start up by replaying all events in the event store. But this could be huge/slow/how will they know when to stop?
Any guidance here would be greatly appreciated!
For any real application. Yes read models are stored in a database. And I mean you probably wouldn’t be doing event sourcing if that wasn’t the case.
Stored in a structure that’s optimised for how you want to read it. Whether that’s relational or otherwise.
So you have two databases? One for the event store and one for read models?
Database. I use the one that stores all of the individual events and the full live record
Do read model stores also get updated in memory as well as in a database for even faster querying? Or is that what a cache is meant to be for?
I feel like event sourcing and CQRS are being conflated a little. I think it’s clear to you but just getting on the same page…
Event sourcing:
Command gets executed, causing 0 or more events to be published. It could also cause other commands to be executed instead (ideally, you don’t have a command affect more than one aggregate but sometimes multiple aggregates need to react)
Events are persisted to an event store
An event store is your ledger of what actions were taken. If you had a checking account, then every debit and credit is an event.
If the events for your aggregate are small enough, you can just get to the current state by replaying the event stream. Or, you could snapshot your aggregate every x events and then read the snapshot and then apply all of the events from that to the head and you’d get your current state.
That’s not practical or efficient for a lot of problems which is why you then introduce CQRS. You treat the event store as your write model and your read store(s) as, well, your read (query) model.
Here’s the thing… you can do CQRS without ES. Think about having a domain model in EF. If you had a contact management system, you wouldn’t load the entire person object and their related data just to show a list of people. You’d have a separate view, or proc, or EF model that loads just what you want for that list.
So, back to your questions…
If the domain is small enough, you can just load the aggregates into memory and call it good. It won’t survive a restart, but that’s ok because you can just reload them again.
So, you store data in dedicated read models. Those only ever get updated by your event store. You don’t care about storage cost because storage is cheap. This opens up a lot of fun possibilities. You can have a read model for just a person’s first and last name.
You’re duplicating data but you’re not duplicating information. The event store tells you that someone’s address changed because they moved. The read model tells you that Bob’s address is 123 anytown usa. Another read model tells you that you have 50 customers in anytown. Another read model feeds a Redis cache so you can quickly query email addresses…
One way to handle the “how do we replay state”… your event store might tell you the last head position for the entire store. You store this on your read side… maybe you group several handlers logically or you track it per handler. Store the last read head position every x events. On restart, look at the current head position for the event store and read from your last known position to head (subscription catch-up). This can work well when you’re hosting your handlers out of proc from where you’re writing.
C# devs
null reference exceptions