Skip to main content

ADR-024: Composition Layer Pattern

  • Status: Accepted
  • Date: 2026-03-08
  • Deciders: HITL/Manager, Product Owner, Cloud Architect

Context

The modules/web module exists to solve a problem that no single upstream module addresses: delivering a complete, production-ready web tier that combines ALB, CloudFront, WAF, and DNS with opinionated security defaults enforced at composition time.

Other modules (alb, cloudfront, acm, s3) follow the derived pattern (ADR-023) — they clone upstream and add value-adds. The web module has no upstream equivalent to clone. Its value is the orchestration and the security opinions applied at the composition boundary.

Key observation: the web module already exists and implements this pattern today. This ADR formalizes what was built, names the pattern, and documents the constraints that govern it.


Decision

modules/web is a composition layer — an opinionated module that:

  1. Wires peer modules (../alb, ../cloudfront) via relative source references
  2. Adds WAF (waf.tf) and DNS (dns.tf) resources directly
  3. Enforces security defaults that span the composition boundary
  4. Exposes a simplified variable surface that hides internal wiring complexity

Composition Wiring

modules/web/
main.tf ← module "alb" { source = "../alb" }
cloudfront.tf ← module "cdn" { source = "../cloudfront" }
waf.tf ← resource "aws_wafv2_web_acl" (direct — no upstream peer module)
dns.tf ← resource "aws_route53_record" (direct — no upstream peer module)
locals.tf ← shared tag merge, computed names
variables.tf ← simplified input surface (vpc_id, subnet_ids, domain_name, ...)
outputs.tf ← composite outputs (alb_dns_name, cdn_domain, waf_acl_arn)

Security Defaults Enforced at Composition Boundary

DefaultLocationRationale
drop_invalid_header_fields = truemain.tf ALB callOWASP header injection prevention
enable_cross_zone_load_balancing = truemain.tf ALB callReliability: AZ failover
HTTPS-only listenerALB + CloudFrontTLS 1.3 minimum per ADR-003 / APRA CPS 234
WAF association on ALBmain.tfAll web traffic protected by default
WAF scope REGIONAL on ALB, CLOUDFRONT on CDNwaf.tfCorrect scope required for each service
enable_deletion_protection = true (ALB)main.tfPrevents accidental ALB deletion in production

What Composition Layer Modules Must NOT Do

  • Duplicate resource logic that exists in peer modules (alb, cloudfront)
  • Introduce their own provider constraints beyond those inherited from peers
  • Publish independently to TFC registry as a standalone module (they depend on peer module versions being available)

What Composition Layer Modules CAN Do

  • Add direct resources for glue services (WAF, DNS, ACM validation records) that have no standalone peer module
  • Override peer module defaults with more restrictive values
  • Expose a simplified interface that consumers use instead of wiring each peer module individually

Consequences

Positive

  1. Operators deploy a complete, secure web tier with one module invocation
  2. Security defaults (WAF, HTTPS, deletion protection) cannot be omitted by composition consumers — they are hardcoded at the composition boundary
  3. WAF and DNS do not require separate peer modules; the composition layer handles them directly, keeping the module count bounded
  4. Changes to ALB or CloudFront security posture propagate automatically to all web compositions on next release

Negative

  1. Consumers cannot swap individual components (e.g., use a different CDN) without forking the composition layer
  2. The web module has a non-trivial variable surface inherited from multiple peers — variable naming conflicts must be resolved explicitly
  3. Relative source references (../alb, ../cloudfront) mean the composition layer cannot be used standalone outside this monorepo structure without path adjustment

Risks

IDRiskProbabilityImpactMitigation
RISK-024-001ALB or CloudFront peer module semver bump breaks composition layerMEDIUMMEDIUMComposition layer pins peer module versions in CI; integration tests gate breakage
RISK-024-002WAF rule set becomes stale relative to OWASP Top 10MEDIUMHIGHQuarterly WAF rule review in task security:trivy scope
RISK-024-003Circular dependency introduced if peer modules reference webLOWBLOCKINGArchitecture rule: composition layer is a leaf node — peer modules must not depend on it

Pattern Boundaries

Module TypeSource StrategyRegistry PublishTFC Standalone
Derived (ADR-023)Clone from upstreamYes — independentlyYes
Composition layerRelative peer refs + direct resourcesNo — depends on peersNo (monorepo only)

Alternatives Considered

  1. Clone a composition upstream (e.g., a community full-stack module): Rejected — no community module enforces the specific combination of ALB + CloudFront + WAF + Route53 with APRA CPS 234 defaults. Building on a generic community module would require more overrides than building the composition directly.

  2. Separate modules for WAF and DNS: Rejected for MVP — WAF and DNS are always present when deploying a web tier in this platform. Splitting them into independent modules adds coordination overhead without business value at current scale. Revisit at Phase 3 when multi-WAF deployments are required.

  3. Single monolithic web module with all resources (no peer module calls): Rejected — violates DRY; ALB and CloudFront are independently useful modules. Duplicating their resource logic inside web would create drift from the independently-tested peer modules.


  • ADR-002: Registry structure — composition layers are monorepo-only
  • ADR-023: Derived module pattern (alb, cloudfront — peers consumed by this layer)
  • ADR-025: Per-component versioning — web module version is independent

Coordination Evidence

  • Product Owner log: tmp/terraform-aws/coordination-logs/product-owner-2026-03-08.json
  • Cloud Architect log: tmp/terraform-aws/coordination-logs/cloud-architect-2026-03-08.json
  • Architecture decisions: tmp/terraform-aws/architecture-decisions/ADR-024-composition-layer-2026-03-08.md