Skip to content

ADR-003: Four Constraint Layers

Context

Domain modeling requires constraints at different enforcement points. A single “validation” concept blurs the distinction between type safety, value correctness, business rule enforcement, and post-state guarantees. This ambiguity produces unclear error semantics and complicates code generation.

The question is how to partition constraints so that enforcement points, failure types, and generation targets are unambiguous.

Decision

Four distinct constraint layers, each with a unique keyword, enforcement point, failure type, and implementation target:

LayerKeywordEnforcement PointFailure TypeGenerated As
Type: TypeNameCompile timeCompile errorStatic type annotation
ValuevalidateCommand constructionErr(ValidationError[])Smart Constructor (private ctor + Result)
Business Rulerequiredecide invocationErr(RejectionError)State-dependent guard in decider
PostconditionensureAfter evolveCompile error (static)Derived from evolve via fusion law

The validate layer implements a Smart Constructor pattern: the command’s constructor is private; the only public factory returns a Result, so an unconstructed command cannot reach decide. The require layer is state-dependent and evaluated at decision time, after construction succeeds.

Formal specification: specs/core.allium (value types, smart constructor entities) and specs/theory.allium (postcondition derivability via F-algebra fusion law).

Consequences

Positive

  • Each constraint maps to exactly one enforcement point and one failure type; error handling in generated code is unambiguous.
  • Domain experts can identify which layer applies: “does the data make sense?” (validate) vs. “does the business allow this now?” (require).
  • Code generation for each layer is independent; the generator can be extended per layer.

Negative

  • Four layers increase the learning curve for new .ddd authors.
  • The postcondition layer requires compiler-level expression analysis; this check is deferred until the expression grammar is fully implemented (see ADR-013).
  • Compiler validation must reject attempts to encode business rules in validate or value checks in require; this distinction check is non-trivial.