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.
91 lines
3 KiB
Markdown
91 lines
3 KiB
Markdown
# Terraform Module Architecture
|
|
|
|
This guide covers designing reusable Terraform modules, composition layers, and the deep hierarchy model for platform engineering at scale.
|
|
|
|
## Module Roles
|
|
|
|
| Role | Responsibility |
|
|
|---|---|
|
|
| **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 smells:**
|
|
- Many loosely typed maps
|
|
- Opaque passthrough variables
|
|
- Outputs that mirror entire provider objects
|
|
|
|
## Suggested File Layout
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `main.tf` | Resources and module calls |
|
|
| `variables.tf` | Typed input contract and validation |
|
|
| `outputs.tf` | Explicit consumer interface |
|
|
| `versions.tf` | Runtime and provider constraints |
|
|
| `locals.tf` | Computed values, naming, shared labels |
|
|
|
|
## 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
|
|
|
|
For platform engineering at scale, use a 5-level module hierarchy:
|
|
|
|
```
|
|
L4: Org Orchestration
|
|
└── L3: Environment Roots
|
|
└── L2: Domain Stacks
|
|
└── L1: Composites
|
|
└── L0: Primitives
|
|
```
|
|
|
|
| Level | Role | Examples |
|
|
|---|---|---|
|
|
| **L0 Primitives** | One resource family, strict contract | VPC, IAM role, S3 bucket |
|
|
| **L1 Composites** | Capability units built from primitives | Networking stack, compute cluster |
|
|
| **L2 Domain stacks** | Bounded business domains | Payments, identity, observability |
|
|
| **L3 Environment roots** | Env-specific wiring and configuration | dev, staging, production |
|
|
| **L4 Org orchestration** | Account/project vending and shared policy | Organization policies, account factory |
|
|
|
|
### Composition Rules
|
|
|
|
- Dependencies flow downward only (L4 -> L3 -> L2 -> L1 -> L0)
|
|
- No lateral imports across the 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
|
|
|
|
## When to Create a New Module
|
|
|
|
Create a new module only when at least one is true:
|
|
|
|
- Reused across 2+ stacks
|
|
- Ownership differs from current module
|
|
- Lifecycle differs significantly
|
|
- Change blast radius needs isolation
|