thomas_p
thomas_p

Reputation: 97

How to propagate consequent changes inside the same Domain entity

In a Domain Driven Design implementation with CQRS and Domain events, when a Command's handler performs a "change" on a Domain entity, and this "change" also causes more "changes" inside the same entity, should all these "changes" be performed by the same Command handler, or is it better to create and send different Commands to perform each of the consequent changes on this entity?

For example, let's say my Domain entity is a Match which has a Minute and a Status properties, and an UpdateMatchMinuteCommand handler updates the Match's Minute value by invoking a class method of the entity. But based on some business logic, inside the class method, this Minute change causes also a change on the Status property of the Match. Shall both of these changes (Minute and Status) take place inside the same class method mentioned earlier?

Or is it more appropriate for the class method, after updating the Minute value, to publish a MatchMinuteChangedDomainEvent, which in turn when handled will send an UpdateMatchStatusCommand, which in turn when handled will invoke another class method of the Match entity to update the value of the Status property?

Which of the above approaches would be more suitable to propagate consequent state changes inside the same Domain entity?

Thanks in advance

Upvotes: 1

Views: 359

Answers (2)

I want to expand complexity of this question. I have an aggregate root - Plan, it has collection of childs entities: Training. Training can be in statuses: pending, approved, rejected. Training has collection of childs entities: Item. Item can be in statuses: pending, approved, rejected.


    class Plan {
      id: string;
      trainings: Training[];
      
      onItemApproved(event: ItemApprovedEvent) {
        // some search item
        // set item status
      } 
    }

    class Training {
      id: string; 
      status: 'pending' | 'approved' | 'rejected';
      items: Item[];
      
      itemStatusChanged(...) {
         // applying rules
         // set status
      }
    }

    class Item {
      id: string; 
      status: 'pending' | 'approved' | 'rejected';
      
      setStatus(status) {
        // set status
        // some propagation
      }
    }

class ApproveItemCommandHandler {
   handle(cmd: ApproveItemCommand) {
      // load aggregate
      // create event
      // apply event on aggregate
      // save aggregate/emit events
   }
}

Rules is:

  1. if one of the items is in status rejected, Training must be set to rejected if it was in another state.
  2. OR if one of the items is in status pending, Training must be set to pending if it was in another state.
  3. OR if all items has approved status, Training must be set to approved if it was in another state.

So i have command ApproveItem { planId: string; trainingId: string; itemId: string }. I have no problem to create event ItemApprovedEvent { planId: string; trainingId: string; itemId: string; state: approved }. And no problem to make Plan.onItemApproved(event: ItemApprovedEvent), and set status of item, but model`s logic will set Training to status approved by rule #3.

How i must provide this change to view model for example?

If i create new TrainingApprovedEvent as a reaction on item`s status set, i see two problems: in a View model i will see inconsistent state that all items approved but training is not and if i am in a EventSourcing context, i will create TrainingApprovedEvent and applying it every time i replayed aggregate from events.

For more complexity imagine that Training`s items are consists of Item and Training types, and status rules applies recursively.

    class Training {
      id: string; 
      status: 'pending' | 'approved' | 'rejected';
      items: (Item | Training)[]
    }

I have some thoughts about some TrainingStatusChangePredictor and use it in command handler to fill event with all changes correctly before applying, but it is some internal logic exposure.

P.S. Don`t be mad about external command handler, it is UseCase application layer, internal command handler in aggregate bounds will cause the same problem.

Upvotes: 0

Luca Masera
Luca Masera

Reputation: 732

I have the feeling that you're going to over complicate the problem. If it's part of the root entity, why should you split the changes in multiple commands?

Reusing Match, Minute and Status, there are (I think...) 3 different situations then:

  1. Examining the domain, you find out that Match contains Minute and Status.

From the analysis you've got the logic that trigger the changes in both the properties of the entity, and per design (DDD) you've put it into Match. Hence, inside the function that update Minute you code also the eventual change of Status.

  1. From the analysis of the domain you find that the Match contains Minute, but not Status, that belongs to another root entity.

This is a case, with 2 entities involved, where using multiple commands is a plausible solution. You update one, generating an event; the event is handled by an handler, that builds another command, which in turn performs the other change. Maybe you do it into a single transaction, or you use a saga; whatever you think it fits your needs.

  1. The domain requires the property Minute into Match, but there're no domain requirements about Status.

Here the Status property it exists, but it's not part of the domain: it could be an extra property, that is used only in the UI, or something else, an helper field, used to perform faster queries. Anyway, Status cannot be inside the entity, because it's not present into the domain.

Given that 2 and 3 are not what you've described, it remains only 1. Hence, I think that both should be changed inside the same action (or function).

A final consideration. You're going to:

  • build a model of the domain based on your business rules, and
  • build it following the DDD indications

That means that:

  • the domain logic should (or must...) stay inside the domain object
  • nothing that belongs to the domain should be 'exported' in external services, except things like SAGAs or complex interaction between root entities (not this case)

so, as I've written at the beginning of the answer:

  • why do you split the domain logic, implementing it within the several object involved in this changes?

It means that wo reads the code, months later, to understand when, how and why Status changes has to go through the changes of Minute, then to the handler, checking also its code (and I suppose has some logic that, if put inside Match, would require less 'hops'), and, finally, the code that really change Status.

Upvotes: 2

Related Questions