Reputation: 28737
I currently have a single event stream per aggregate root and twoaggregate roots, Room
and RoomType
.
The behavior of a Room
depends on what RoomType
it is. In order to separate both aggregates, The RoomType
is just represented as a roomTypeId in the Room
aggregate. A change of RoomType
is represented by a RoomTypeChanged
event.
The RoomTypes
can be managed separately and need to be in a different aggregate.
Now consider the following use case:
When a user invalidates a RoomType
, all Rooms
that have that Roomtype
should switch to a fallback RoomType
.
I have thought of several approaches, but all of them seem to be problematic:
Have an event listener listen for a RoomTypeInvalidated
-event, and send a SwitchToFallbackRoomType
on all Room
-aggregates that have that Roomtype
.
How would I do this though? There's no way to know which aggregates have that Roomtype
unless I access my readmodel, which seems incorrect.
Even if I were to load all aggregates, there's no way to load only the aggregates of that type as I can't load a subset of all streams (using geteventstore).
When reapplying the RoomTypeChanged
-events to the Room
aggregate, instead of just applying it, do a check to see whether that RoomType
still exists, but then again, how would I know which RoomTypes
exist (I'd be in the same situation as 1, but inverted)? Also, it seems wrong to put in logic on reapplying events, they should just represent state changes I think.
How would you solve this?
Upvotes: 5
Views: 290
Reputation: 1070
We are working with the same issue, also rooms and roomtypes. The solution you are having problems reaching is probably mapped from the relational where this is a non issue. It's hard and confusing to think start thinking behavioral in an otherwise very data-driven field. I think the to solve the problem you have to challenge the proposed solution.
When re-thinking it, we ended up with aggregates like RoomAssignment and RoomAllocationSchedule, and discovered that RoomType is mostly used to communicate room-features to external channels / OTA.
Modelling the behavior we need really helps (since the consistency bounderies starts to make sense), instead of modelling data and trying to enforce relational consistent behavior on it...
When we really need consistency across aggregates, we model a process-manager/saga that in itself is transactionally consistent, and message the aggregates. As such, we have a "RoomAssignmentDirector" aggregate, that makes sure that a room is allocated for a roomstay before it is assigned to the roomstay etc.
Hope it helps.
Upvotes: 1
Reputation: 4634
Solving this problem by making sure that data is completely consistent every time is working against the design. Event Sourcing presumes the system is eventually consistent so having inconsistencies at the read model is normal and expected.
The best way to solve this would be to query your read model for all rooms with the new invalidated TypeId (your solution 1). After updating all those rooms, there is a possibility that there are a few stragglers that are were not updated, so you re-run the event handler a few moment later (let's say a minute).
At that point (assuming that your Commands checks the validity of the room type at Room creation) it is impossible for new rooms to be created with the old room-type and the read model for the stragglers should have already stabilized. So re-running the code will update everything remaining.
Upvotes: 0
Reputation: 445
Why not use a process manager per roomtype and have the list of rooms as a property of the process manager?
Querying the read model from a service or from the command handler will not guarantee a consistent framework. What if a room was assigned to the invalidated room type but the read model wasn't updated yet? ( that's sort of the scenario I'm facing)
Upvotes: 0