# 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