Best way to design Event Handlers for the Read Model in a ES/CQRS application

423 views
Skip to first unread message

Leif Battermann

unread,
Mar 5, 2017, 11:45:24 AM3/5/17
to DDD/CQRS
I can't figure out the best way to implement event handlers for the read model of an Event Sourcing / CQRS application. Even though this might be a common concern, I could not find a satisfying answer so far. Most examples I found are so basic that the issue I'm having is not really a concern.

The Write-side

The write-side is pretty straight forward. When a command is handled, the event stream for the aggregate will be replayed to get the current state. Next it will be decided if any events should be produced or not. The replay logic basically contains most of the domain logic and is (let's assume) pretty complex. The domain could be e.g. a game and a command contains the move. If the move is valid it, changes the state of the game according to the rules.

The event that is produced contains no redundant information, usually only the delta according to the previous state. In the case of a game it is usually enough to persist the current move. Something like:
   
Played { gameId = 55555; playerId = 48939; position = (25, A); shape = Square }

So far so good.

The Read-side

I see different possibilities to implement the event handlers for the read model:
  1. Read current state from the read model database and apply the information from the event that triggers a complex state change (e.g. a move in a complex game)
  2. Access the repository of the event store and read the current state from there
  3. The repository of the write-side emits non-persistent events that contain the complete state of the aggregate that was changed on each change
  4. Try to find a better representation of the delta and propagate it
In my opinion the first approach has a major downside because it leads to duplication of domain logic. Of course it is not a big deal if we have an event like
EmailChanged { customerId = 42; newEmail = "f...@example.com" }
or if the event represents a mark on a tic-tac-toe board e.g. We do not need to validate the incoming events anymore because that has already been done (events represent facts that have happened). But if the event leads to a complex change of state the rules of how to get to that state have to be implemented again. And the more complex those rules are, the more I dislike this approach.

The second approach I find also not good because it creates a dependency on the write-side. I always think of the write and the read-side as two pretty decoupled systems where only the read-side consumes events that are produced by the write-side. A direct HTTP call to the event store e.g. would couple them much more together. It would also mean, that the event type and the event payload (except the aggregate id) can be completely ignored. An incoming event then would only indicate a state change. The state is then taken directly from the event store. This feels wrong to me.

Also the third approach feels wrong. It creates a logical dependency on the read-side. How should the write-side know what kind of state representation the read-side needs or might need in the future? I 'm sure there are more disadvantages like rebuilding the read model will be more difficult or large event payloads produce more network throughput e.g.

The last approach is vague. However, it can lead to a good solution IMO. If the new state is the result of a heavy or complex computation, it seems like a good idea to persist the result of this computation, so that the calculation does not have to be repeated each time the aggregate is loaded and the events are replayed. Anyway it is not completely satisfying. Coming back to the game example, if the move causes almost the whole game context to change, it will be extra work to figure out exactly what changed and what didn't. This accounts to applying teh change to the aggregate or read model as well as to producing the event payload. In this scenario it might be way easier to just propagate the complete state. But this not good either for (I guess) well known reasons.

Do I have to make a trade off and decide for one of the above designs depending on the special needs of the domain I'm working with? 

Or is there a better solution I'm not seeing?

Thanks!

Peter Hageus

unread,
Mar 6, 2017, 3:19:45 AM3/6/17
to ddd...@googlegroups.com
I do 1 or 4.

But as always, it depends.

If a write operation results in something more complex than +/- X, I include it in the event. Upside is that projections can be a lot simpler, downside that fixing bugs might be harder.

/Peter

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
Visit this group at https://groups.google.com/group/dddcqrs.
For more options, visit https://groups.google.com/d/optout.

Ramin

unread,
Mar 6, 2017, 8:49:48 AM3/6/17
to DDD/CQRS
I agree, 1. or 4. Your objection to 1. is not entirely correct. The domain logic does not get duplicated on the read sinde, unless it is mainly CRUD, in which case things are quite simple. As an example, a command may tell the domain to bill a project. The billing rules may be quite complicated. They may depend on who did what, where and when (weekdays, weekends, etc.). The write side takes care of this and in the end raises events that include all relevant information of what happened. All "how" these things happened is part of the write side and does not belong in the read side. In the simplest case, the events simply have single billing positions whith a $ price. Building the read side from that is simple, whereas the domain logic to produce these events may be quite complex.

Like Peter Hagues stated, some of the "how" the events were generated may be included in the events. But the interpretation of the data inside the events for the read will still usually be straight forward, and there is no need to implement (all) domain logic on the read side. Of course the read side required the right amount of logic to understand the events.

Cheers
Ramin

Leif Battermann

unread,
Mar 20, 2017, 3:51:54 AM3/20/17
to DDD/CQRS
Thanks, Peter and Ramin, for your answers. This really aligns with what I was thinking.

The concrete example I had in mind was the domain of SameGame: http://www.js-games.de/eng/games/samegame/lx/play

I got these commands (see source code):

sealed trait Command
case class StartNewGame(board: Board) extends Command
case class RemoveGroup(position: Position) extends Command

Now, when a group is removed, the board rearranges. Often this results in changes of many cells of the board. The logic of rearranging is not very complex, but I really don't want to duplicate this to build the read model.

So my options are, either put only the changed cells in the events, or put the whole board state in the event.

I went for the latter because I like the simplicity. Do you think this is reasonable?

sealed trait Event
case class GameStarted(id: GameId, board: Board) extends Event
case class GroupRemoved(id: GameId, position: Position, board: Board, score: Int) extends Event
case class GameFinished(id: GameId) extends Event

Maybe you think this is not a big deal? I agree. But I think this teaches me an important lesson on how to model events and the underlying principles and best practices.

Thank you! 

Ben Kloosterman

unread,
Mar 20, 2017, 7:55:09 PM3/20/17
to ddd...@googlegroups.com
Likewise.. 1 or 4. As far as logic its better to have as much logic in the aggregates and use chunkier events.

"In my opinion the first approach has a major downside because it leads to duplication of domain logic." . 

Can you elaborate ? Most read models are pretty simple views and the logic is mostly data store related .  Also in the Readmodel there is nothing stopping you having new MyabcHelper() Or MyImmediateBehaviour().DoXyzBehavior() and share that code.

Regarding your board example id put the board in ( or all changes to the board) unless its going to be 10s of Megs. Some message providers do fail with large messages. If it becomes a problem change it. . 


Leif Battermann

unread,
Mar 21, 2017, 3:58:14 AM3/21/17
to DDD/CQRS


"In my opinion the first approach has a major downside because it leads to duplication of domain logic." . 

Can you elaborate ?

Yes, my original post is not very clear here.

The game is a good example if we only include the position of the played move in the event: GroupRemoved(id: GameId, column: Int, row: Int).

This approach is totally reasonable in a game like Tic-Tac-Toe because all the event handler does is to put a mark at the played position.

However, with SameGame when a move is played the board rearranges in a non-trivial way according to the game rules. Should these rules be implemented twice, in the BL layer and in the event handler of the read side? This would be a duplication of domain logic.

 

Regarding your board example id put the board in ( or all changes to the board) unless its going to be 10s of Megs. Some message providers do fail with large messages. If it becomes a problem change it. . 


Thanks. Good advice!

Ramin

unread,
Mar 21, 2017, 4:47:57 AM3/21/17
to DDD/CQRS
As long as the event doesn't simply state which cells were removed and the read model then needs to calculate how that affects all the other cells, either solution makes sense. The entire board state in the event will make it quite easy for the read model to show the new board state. Only including changed (not removed) cells is also simple enough. The read model does not have to know how or why the cells were changed, simply needs to apply these simple changes to get to the new read model state.

As usual, it depends and each solution will have tradeoffs. Since the game is the aggregate, and has a limited and relatively small number of states it will go through, I would personally opt for including the entire state in the events.

Cheers
Ramin

Ramin

unread,
Mar 21, 2017, 4:49:50 AM3/21/17
to DDD/CQRS
Just to make clear, " Only including changed (not removed) cells..." should read " Only including changed (not just removed) cells..."
Cheers

Leif Battermann

unread,
Mar 21, 2017, 4:51:36 AM3/21/17
to DDD/CQRS
Makes very much sense. Thank you!

Ben Kloosterman

unread,
Mar 21, 2017, 8:42:25 AM3/21/17
to ddd...@googlegroups.com
On Tue, Mar 21, 2017 at 6:58 PM, Leif Battermann <leifbat...@gmail.com> wrote:


"In my opinion the first approach has a major downside because it leads to duplication of domain logic." . 

Can you elaborate ?

Yes, my original post is not very clear here.

The game is a good example if we only include the position of the played move in the event: GroupRemoved(id: GameId, column: Int, row: Int).

This approach is totally reasonable in a game like Tic-Tac-Toe because all the event handler does is to put a mark at the played position.

However, with SameGame when a move is played the board rearranges in a non-trivial way according to the game rules. Should these rules be implemented twice, in the BL layer and in the event handler of the read side? This would be a duplication of domain logic.

Best have it in the Aggregate . Note however some games will fit poorly onto aggregates especially if there is a lot of shared state  OO, Event Processors and Actor type models may suit them better. 

If the message gets too big consider tight packing a byte array with the pieces being the bytes. 

Regards,

Ben 

Thomas Schanko

unread,
Mar 21, 2017, 8:42:26 PM3/21/17
to DDD/CQRS
I'd tend to think about the relation between the command and the resulting events rather then about event handlers first. It`s hard to reason about this relation without knowing a bit more about the context (i.e. the aggregate handling the command). The "RemoveGroup => GroupRemoved" approach does not reflect much of the business logic you mentioned, nor does it seem to reflect multiple changes to the state of your aggregate. The command seems to have passed validation... Maybe it`s a good idea to dig deeper into the code that handles this command. I'd try to find all the small changes it applies to your aggregates state and consider emitting an event for each of them.

Leif Battermann

unread,
Mar 22, 2017, 4:23:53 AM3/22/17
to DDD/CQRS
Hi Thomas,

very good point, it is definitely much better to model the commands and resulting events first.

So one alternative design could be sth. like this:

Command:
  • PlayAtCell(position: Position)
Which could result in:
  • PlayedAtCell(position: Position)
  • GroupRemoved(...)
  • CellsShiftedDown(...)
  • ColumnsShiftedLeft(...)
  • PointsScored(...)
  • [optionl] GameFinished(...)
To me that makes sense. However, it would make the process more complex, maybe needlessly in this particular case. Why describe all intermediate steps, in this fine grained manner? What is the business requirement that drives this design?

Thomas Schanko

unread,
Mar 22, 2017, 6:16:17 PM3/22/17
to DDD/CQRS
Hi Leif,

It is always a good idea to ask for the justifying business requirements before adding complexity to a system. So, which requirements where the drivers for introducing ES and CQRS in the first place? What problem did you try to solve? One of your questions was, how to avoid duplication of business logic. I suggested an approach which puts logic first, in order to emitt relatively fine grained and meaningfull events. Handling those events should be very easy; it's merely apllying atomic changes to some sort of state. You can apply them to a given read model just the same way you apply them to your aggregates state in order to reconstitute it. So for example, your board read model is only interested in the rows / columns moved events; there is no need to worry about PointsMade or GameEnded in order to draw the board.
Adding complexity is a tradeoff, make shure you gain something worth the effort.

Thomas

Reply all
Reply to author
Forward
0 new messages