Skip to main content

Composition and DI Clinic

Retrieval Prompts

  1. Describe Composite in one sentence. What property does it guarantee to clients?
  2. Describe Decorator in one sentence. What does it not change about the wrapped component?
  3. Describe Proxy in one sentence. Name two kinds and their intents.
  4. Describe Dependency Injection in one sentence. What is its opposite, and why is that usually worse?
  5. Describe a composition root in one sentence. Where does it live in a typical HTTP service?

Compare and Distinguish

Separate these pairs clearly:

  • Decorator vs Proxy
  • Composite vs Decorator
  • DI vs service locator
  • DI vs DI container
  • Constructor injection vs setter injection
  • Composition root vs "bean configuration"

Common Mistake Check

For each statement, identify the error:

  1. "We use a Decorator to translate our HTTP response into domain objects."
  2. "This class is a Proxy because it just forwards calls to another object."
  3. "Our OrderService looks up its PaymentGateway from a service locator so we can swap it in tests."
  4. "Constructor injection is too verbose, so we made all dependencies public fields and set them after construction."
  5. "Our composition root is a class that every service imports."

Mini Application

Build a small service with all three patterns and a composition root.

Step 1 -- Domain service

Write a NotificationService with one method, notifyUser(userId, template, data), that depends on:

  • a UserRepository (interface)
  • a TemplateEngine (interface)
  • a Transport (interface)
  • a Clock (interface)

Use constructor injection. No new for any of these inside the class.

Step 2 -- Composite

Represent a Channel as Composite:

  • Channel interface with send(user, message)
  • EmailChannel, SmsChannel (leaves)
  • MultiChannel (composite) that delegates to a list of children

Step 3 -- Decorator pipeline

Wrap Transport in decorators:

  • LoggingTransport -- logs every send
  • RetryingTransport -- retries N times on failure
  • RateLimitingTransport -- allows at most K/sec

Compose them in a specific order at the composition root and write one unit test that pins the ordering.

Step 4 -- Proxy

Add an AuditProxy that wraps the NotificationService and records each call. Use it in an integration test to assert outgoing calls; keep the real service untouched.

Step 5 -- Composition root

Write a single buildApp(env) function that:

  • loads config once at the boundary
  • constructs concrete infrastructure (in-memory repo, fake SMTP, fake SMS)
  • wires decorators in a clear top-to-bottom order
  • returns the domain service

Write a second buildTestApp() that swaps the fakes for even simpler test doubles.

Reflection

  1. Count the new keywords in your domain layer. The answer should be zero (except for value objects).
  2. Delete the composition root temporarily and observe which call sites break. The fewer, the better.
  3. Name one decorator in your pipeline that you would remove if this were a prototype and justify keeping it.

Evidence Check

This page is complete only if you have:

  • a service that uses only injected interfaces
  • a working Composite tree
  • a Decorator pipeline with at least three layers and an ordering test
  • a Proxy exercising an access-control or auditing concern
  • one composition root per runtime (prod and test) with zero cross-imports from domain code