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