Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doesn't updating read models using domain events duplicate business logic?

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

like image 955
duselbaer Avatar asked Dec 14 '22 01:12

duselbaer


2 Answers

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.

like image 190
VoiceOfUnreason Avatar answered May 14 '23 15:05

VoiceOfUnreason


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:

  • "how many cancellations were there in the last hour?"
  • "what are the most common reasons for cancellations?"
  • "is there a correlation between the length of journey and the % of cancellations?"
  • etc...

The write side of the domain is there to enforce business rules and invariants.

like image 38
tomliversidge Avatar answered May 14 '23 15:05

tomliversidge