Sunday, February 5, 2017

Aggregates and RFC 2119.

The language used to describe the relationship between aggregates and commands is a confusing one.

The usual language is that the aggregate protects the business invariant -- one can reasonably read such at thing, and come away with the idea that aggregates are going to be throwing some flavor of DomainException when a command would break the rules.

Fortunately, we have experts trying to offer guidance.
Commands should not fail in collaborative domain
I struggled with understanding this for quite a while, because it sounded to me as though he was talking about checking that the command is valid before dispatching it to the domain.

The language that turned my brain around, was to consider that the aggregate is responsible for restoring the business invariant.

Udi Dahan, observing that Race Conditions Don't Exist, observed:
A microsecond difference in timing shouldn’t make a difference to core business behaviors. 
Reordering two commands should not change the behavior that you observe.  In particular, you shouldn't be rejecting a command that you would accept if the ordering were different.

This turned up recently in a calendar domain, where the aggregate is responsible for ensuring that events don't overlap.  So let us imagine an attempt to schedule two conflicting events, A and B.  In a naive implementation, the order used to process the commands will determine how the conflict is resolved.

This is an interpretation of the invariant that is consistent with MUST or MUST NOT from RFC 2119.  And that's fine, if that's what the business really needs.  But it's not the only way to interpret a business invariant.

This model of the domain is analogous to SHOULD or SHOULD NOT. We don't want conflicts to happen, but if we do the aggregate has the responsibility of tracking the conflict and whether or not it has been resolved. In a sense, the responsibility of the aggregate is to detect and track conflicts -- maintaining the schedule is a side effect.

To reduce the rate of conflicts, the caller is expected to make a good faith effort to ensure that there are no conflicts before dispatching the command.  After all, if the aggregate is tracking events and conflicts, then the caller can check for both before dispatching the command.  When the data that the caller is working from is stale, that judgment may be off, but the aggregate has a fail safe available to cover that contingency.

A similar example might occur in banking, where instead of rejecting a transaction, the bank instead invokes an overdraft contingency.

If your requirements include a MUST NOT, then push back and check: what is the cost to the business of allowing the behavior to occur?  Especially in business that run on human input, the processes that run the business are designed to mitigate these kinds of problems.  We can do the same thing in code.

Because SHOULD NOT is going to have much nicer scaling properties.

No comments:

Post a Comment