In the Red Book Vernon models BacklogItem and Sprint entities as seperate aggregates. I see advantages of this approach but there is one case that I can't get my head around. For example, I need my Sprint aggregate to enforce maximum items assigned limit. Moreover the BacklogItem must be aware of assigment fact to make sure it is not assigned to more than one Sprint. So assigning BacklogItem to Sprint changes two aggregates in one transaction which is not what we want to do. I can not see any good approach that solves this issue. Expanding aggregate implies making BacklogItem internal part of Sprit. Which does not make any sense because of nessesity of using it inside other aggregates (Release, Schedule). Another way I have come up with is to use eventual consistency and just alert administrator about double assigment of the BacklogItem. But I perceive it as important aggregate invariant and I would like to gain opportiunity to impose that explicitly.
Another way I have come up with is to use eventual consistency and just alert administrator about double assignment of the BacklogItem.
My guess is that this is the correct answer.
I need my Sprint aggregate to enforce maximum items assigned limit.
There's an important consideration that you may be overlooking: who decides which BacklogItems are added to a Sprint? Is that a decision that the model is making for itself, or is that decision made by a human being (or some other entity outside the model)?
Because for the most part the model should not be discarding decisions made by human operators; and it shouldn't force the human operators to context shift to jump through the model's hoops.
There is no point to run that command if we know in advance that we exceeding Sprint items limit. If the Command Handler will perform that checking: $sprint->hasSpaceFor($item); wouldn't it be considered as knowledge leaking?
There are a couple things to consider here.
One is that data that's not part of the aggregate is stale; there could be another command handler changing that data while you are checking it. That doesn't mean that checking it is wrong, but it does mean that checking it in the command handler isn't obviously better than checking it somewhere else.
Secondly, your check of the invariant depends on the order in which messages arrive. The human operator may have decided to remove an item from a sprint to make room for the new one -- but if the order of messages gets changed in transit (unreliable messaging transport), then the model ends up rejecting what should be a valid command. That's not good.
Alternatively, the messages may be arriving in the order they were dispatched. The human operator knows the two operations cancel out, and therefore it shouldn't matter what order it happens -- but the model insists that the decisions be reported in a specific order. That's hoop jumping -- the model is making the job harder, instead of easier.
And furthermore, the value to the business of getting the right item into the sprint may be more valuable than figuring out which one to remove.
Key insight: the human operator is working with the priorities of the business now, but the model running in production reflects the priorities at the time it was written -- in other words, the model has captured past priorities. So you want to be careful about having the model veto the operator.
More to the point, you want to understand how business value is derived when vetoing the messages you get from the human operator, and decide where that responsibility should be managed -- maybe it's a UI concern (trying to reduce the high cost of data entry errors?), rather than a domain model concern.