I am currently thinking about the granularity of domain events. As far as I understood it is a good pattern to start with a 1:1 relation from commands to domain events so that a domain event expresses something the user did.
In my example there is a journey (of a train or a bus) which consists of a sequence of stops. Each of these stops has a timestamp assigned when it is going to happen. Now the user may cancel this journey which results in all the timestamps being removed (simplified) from the journey's stops.
What I would do is to emit a "JourneyCancelled" event.
Now if there's a read model providing a list of stops for the journeys - how would the read model handle this event? It would probably also reset the timestamps for every single stop of the journey which means it duplicates logic there (and in every other read model which handles this event).
I am interested in the fact that a journey got cancelled but I am also interested in the details which happened.
What am I doing wrong regarding the domain events?
Ronny
As far as I understood it is a good pattern to start with a 1:1 relation from commands to domain events so that a domain event expresses something the user did.
Relearn that -- 1:1 is common, because many commands modify only a single entity and don't require any compensating actions, but it isn't general. You'll often need multiple events to maintain the invariant when the user proposes a change.
You also want to be careful about "something the user did". If your message is describing something the user (in the real world) has done, then that message is an event, not a command. Commands are the user asking the model to do something (important touchstone - is the model allowed to say no?).
In my example there is a journey (of a train or a bus) which consists of a sequence of stops. Each of these stops has a timestamp assigned when it is going to happen.
Keep in mind as well that "aggregates" are almost always information resources -- not the Traveler, but the TravelerProfile. Not the Journey, but the Reservation or the Itinerary.
In this case, "when it is going to happen" implies Schedule or Itinerary as a concept that should be explicit in your model (not yet clear if this thing is the root of your aggregate, or an child entity within it).
You also need to think about whether stops (I like Legs, from my work in travel software) are values, or entites with current state.
Now the user may cancel this journey which results in all the timestamps being removed (simplified) from the journey's stops.
Hint: when asking for modeling advice, avoid simplifying; good modeling requires understanding the constraints.
It's not at all clear why cancelling a journey should remove timestamps from a schedule.
Trying to guess at an analogy for what you want; in air travel, a trip is composed of legs. If we cancel the trip, we'll also want to cancel payment of the tickets for the legs, release the seat assignments, and so on.
So picking the seat assignment as specific example; the fact that cancelling the trip implies releasing the seat assignment is an invariant, so there should be an explicit event announcing the seat assignment has been cancelled (as of timestamp and so on).
So read models tracking seat assignment should be listening for seat assignment events, and updating the seat map appropriately (explicit), rather than listening for JourneyCancelled and inferring the changes to the seats (implicit).
In other words, we escape the trap of duplicating the business logic by making the event stream more explicit. Notice the extra decoupling we get for free: because the read model can listen for events specific to the entities it cares about, we can redesign the aggregates in the write model without having to worry about impact on the read models.
Now the user may cancel this journey which results in all the timestamps being removed (simplified) from the journey's events.
Events should be immutable, you should never remove data from them based on subsequent events.
Now if there's a read model providing a list of stops for the journeys - how would the read model handle this event? It would probably also reset the timestamps for every single stop of the journey
What is this read model for? What questions are you trying to answer? Does the business need a read model that shows all predicted stops on journeys, or would a JourneyCompleted
event be more appropriate? (or more granular, a LegCompleted
event).
Does it need to know which journeys are then cancelled, but maintain the predicted stops?
One approach is to split the events up a bit more as alluded to above:
JourneyStarted
(could include all predicted stops)LegCompleted
(stop name/id, timestamp etc)LegCompleted
JourneyCompleted
or
JourneyStarted
LegCompleted
JourneyCancelled
etc...
I'm not clear what you're trying to do in either your read or write side. Read models are usually aggregations of the data, designed to answer questions like:
The write side of the domain is there to enforce business rules and invariants.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With