Reputation: 97
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
Reputation: 1
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:
items
is in status rejected
, Training
must be set to rejected
if it was in another state.items
is in status pending
, Training
must be set to pending
if it was in another state.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
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:
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.
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.
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:
That means that:
so, as I've written at the beginning of the answer:
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