In a DDD approach, suppose I have an Order
(this is my aggregate root) and a Line
(a descendant of an Order
). In both objects I have private setter for properties and a builder that handle creations of the cluster of object.
As far as I know every action on the cluster goes through the aggregate root. Is this right?
For instance, if I need a command to hide a line I can’t have simply an hide()
method on the Line
object, but instead I should have an hideLine()
method in the aggregate root that traverse the cluster of object down to the Line
.
At this point I imagine a scenario like that:
// in the aggregate root
public void HideLine(Line line){
line.Hide();
}
// in the Line class
public void Hide(){
this.Hidden = true;
}
but in this way someone could do something like:
Order orderA = _repo.getById(id_A);
Order orderB = _repo.GetById(id_B);
orderB.HideLine(orderA.Lines.First());
with unknown side effects (right now the side effect is clear, but in a more complex scenario this could create headache).
So I could take this counter-measures:
- Accept an id as parameter for
HideLine
mehtd (i.e. public voidHideLine(Guid lineId)
) - Check in
HideLine(Line line)
method thatline
is actually inOrder.Lines
collection
How I handle consistency issues? Are my solutions meaningful or I have completly miss the point?
Post scriptum after comment(s)
@Songo: The Order
aggregate (note: in my real scenario it isn’t an Order but a more complex cluster) starts a saga/process manager during its life that oversees the Order
publishing process (try to imagine that in a real scenario an order need to be published). As soon OrderPublishingRequestEvent
is catched the saga starts and try to validate the Order
checking if each Line
is valid, and if not the line will be hided.
Thus my command handler is something like:
public void Handle(OrderValidationCommand message)
{
// get the aggregate
Order order = _repository.GetById(message.OrderId);
// call a service to make validation
foreach (Line line in order.Lines)
{
var result = validator.Validate(line);
if (result.IsValid)
order.HideLine(line);
else
// do something else
}
order.Validate(); // raise event ValidatedOrderEvent
_repository.Update(fornitura);
}
7
The Command Handler doesn’t seem like the best place to put line hiding logic. It should contain orchestration but not domain rules.
Is there a way you could give the validator to Order
and have it validate itself, as a single action executed in one transaction ? That would solve the Line
access problem at the same time.
Maybe this would be more in line with the domain too, since I doubt you have “hide a line” as part of your ubiquitous language. It seems to me more like a side effect of validating Order lines, not a domain requirement.
2