DDD layers and CQRSThe aggregate owns invariants
No narration yet
Module 9 · Lesson 115 min

The aggregate owns invariants

The domain layer holds the rules. An aggregate root is the object that owns a consistency boundary and its invariants: you load it whole, call an action on it, and it either succeeds or returns a failed Result. The application layer is a thin orchestrator: load the aggregate, call the action, save. It must not pre-compute domain decisions and hand them in; that would make the aggregate a hollow validator over caller-supplied data (convention D1).

A thin application use case (real RevertDisruptionDecision)
public sealed class RevertDisruptionDecision
{
    private readonly IFlightDisruptionAggregateRepository _repo;
    private readonly Func<DateTime> _utcNow;   // injected clock = testable time

    public async Task<Result<FlightDisruptionAggregate.RevertDecisionResult>> Execute(
        RevertDisruptionDecisionRequest request)
    {
        var summary = await _repo.LoadRevertSummary(request.EjdpUniqueFlightKey);
        var aggregate = FlightDisruptionAggregate.ForRevertFlow(
            snapshotExists: summary is not null,
            hasActiveDecision: summary?.HasActiveDecision ?? false);

        var result = aggregate.RevertDecision(flightNumber, _utcNow());
        if (!result.IsSuccess) return result;         // guard, then continue

        var updated = await _repo.MarkAsReverted(summary!.SnapshotId, summary.EjdpUniqueFlightKey, result.Value.RevokedAtUtc);
        return updated ? result : Result<...>.Failure(new DecisionNotFound(...));
    }
}
Practice

Try it yourself

Do

Trace a use case end to end

See the load / act / save shape and the injected clock.

Tick every step to confirm you did it.