Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
stop-slop, taste-skill, terrashark had embedded .git dirs causing Woodpecker clone to fail on submodule update.
2.5 KiB
2.5 KiB
Module Architecture
Use this guide when designing reusable modules and composition layers.
Module roles
primitive module: wraps one resource family with strict interface.composite module: assembles multiple primitives for a deployable capability.root composition: injects environment values and wiring only.
Keep business policy out of primitives when it is environment-specific.
Contract design
A good module contract has:
- strongly typed inputs
- defaults only for safe/common behavior
- explicit outputs for consumers
- preconditions for invariants
Bad contract smell:
- many loosely typed maps
- opaque passthrough variables
- outputs that mirror entire provider objects
Suggested file layout
main.tf: resources and module callsvariables.tf: typed input contract and validationoutputs.tf: explicit consumer interfaceversions.tf: runtime and provider constraintslocals.tf: computed values, naming, shared labelsREADME.md: short usage and contract notes (if repository policy requires docs)
Composition rules
- pass only required values into child modules
- avoid circular dependencies and hidden ordering
- prefer data flow via input/output over broad
depends_on - keep module count manageable; over-fragmentation hurts maintainability
Deep hierarchy model
Use this when a platform grows beyond a single composition layer.
Hierarchy levels:
- L0 primitives: one resource family, strict contract
- L1 composites: capability units built from primitives
- L2 domain stacks: bounded business domains (payments, identity, observability)
- L3 environment roots: env-specific wiring and configuration
- L4 org orchestration: account/project vending and shared policy baselines
Composition rules:
- dependencies flow downward only (L4 -> L3 -> L2 -> L1 -> L0)
- no lateral imports across same level without an explicit interface contract
- cross-state data flow is via explicit outputs or approved remote state access
- each level owns its state boundary and apply lifecycle
- environment roots should not embed business logic; keep it in L2/L1
Decision aid:
- add a new level only if ownership, lifecycle, or blast radius requires it
Module release discipline
- tag module versions
- use bounded version constraints in consumers
- run compatibility tests before raising lower bounds
Decision checkpoint
Create a new module only when one is true:
- reused across 2+ stacks
- ownership differs from current module
- lifecycle differs significantly
- change blast radius needs isolation