1

It's clear that an entity should never be in an inconsistent state; if a change would violate an invariant, a domain object should reject the change. But what if it's left in a valid, but inappropriate-for-the-input state?

For example, consider a calendar aggregate that supports a "delay all meetings" feature – upon delay, each meeting entity within the calendar has its time moved a given amount into the future. To implement this, a calendar aggregate might sequentially delay each meeting which would edit its time window.

Now imagine that one of a series of meetings has some internal invariant that means it cannot be delayed. It would reject a delay operation and the calendar would maintain that rejection. But what if other meetings have already been delayed? It's likely that each individual delay prior to this one has left the whole aggregate in a perfectly valid state! However, that aggregate state represents neither the result of the operation nor the original state.

In db-backed applications, the exception generally prevents persistence of the changed aggregate, so the partial change is ultimately ignored. Still, it feels wrong to leave the in-memory aggregate in this unwanted (albeit internally consistent) state.

I've considered these options, but they all have their own problems:

  1. Operate on copies of the meeting (either as immutable behavior or by cloning first), then replace the full collection of meetings only once all have succeeded. This seems to contradict the nature of entities.
  2. Perform a "can delay?" check for each meeting before actually delaying any of them. This may duplicate a significant amount of effort.
  3. Revert the changes that were made before the failed change. Unless there's a domain reason to "un-delay" a meeting on its own, this mixes cleanup code into domain code. Plus, cleanup is a task in its own right with its own reasons to fail. Maybe some changes are irreversible?

The other issue common to these approaches is that it feels leaky for the child to change the way it behaves based on the usage patterns of the consumer. The problem compounds when entities are nested and combined in different ways.

Is there consensus or best practice that covers this situation?

Or, as an alternative formulation, where should we direct our attention when facing a situation like this – Such as prevention or rollback at the domain level vs. rejection of changes in the application layer?

2
  • 1
    As long as you don’t persist the changes, what is the actual problem? I.e. Load the aggregate. Invoke operation on aggregate that changes in-memory data, but eventually results in an exception. Inform the caller that the operation failed. Dispose in-memory aggregate.
    – Rik D
    Dec 22, 2021 at 20:11
  • @RikD In terms of pure practicality, I can definitely just avoid persisting the changes. Nevertheless, the in-memory aggregate still exists in an incorrect state and the consumer needs to know to get rid of it. I wondered if, conceptually, I was missing something to mitigate or address that situation. Is there something mentioned in a DDD book, or any "good subjective" answers for techniques? Dec 23, 2021 at 14:05

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.