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:
| Layer | Keyword | Enforcement Point | Failure Type | Generated As |
|---|---|---|---|---|
| Type | : TypeName | Compile time | Compile error | Static type annotation |
| Value | validate | Command construction | Err(ValidationError[]) | Smart Constructor (private ctor + Result) |
| Business Rule | require | decide invocation | Err(RejectionError) | State-dependent guard in decider |
| Postcondition | ensure | After evolve | Compile 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
.dddauthors. - 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
validateor value checks inrequire; this distinction check is non-trivial.