ProgramGuru

Dependency Injection Pattern

Dependency Injection: separate a useful responsibility

See the responsibility boundary

Composition rootChooses supported implementations
ConsumerDeclares the capability it needs
Injected dependencyImplements that contract
Test or deploymentChooses a deliberate lifetime
Composition root: Chooses supported implementations → Consumer: Declares the capability it needs → Injected dependency: Implements that contract → Test or deployment: Chooses a deliberate lifetime

An order service creates its own email client, making it hard to test without contacting a real provider. The service needs a capability, but should not secretly own every detail of choosing and constructing it.

This application-pattern lesson connects local organization with the system contracts discussed elsewhere in the course. The code is an original small local example, not a production persistence or web framework.

What you’ll learn

  • Explain the responsibility Dependency Injection separates.
  • Follow its local example and identify the implementation boundary.
  • Compare related patterns and test the required guarantees.

How the responsibility is divided

Dependency Injection supplies dependencies to a consumer from an explicit assembly boundary. The consumer states what contract it needs; the composition root chooses and connects implementations. Constructor, function-parameter and framework-supported injection are common forms.

The example supplies a notifier to the order service. A test can use a recording implementation without changing the order behaviour. A container is optional; explicit function arguments can implement the responsibility.

Follow a small JavaScript example

Read the program and compare the expected output. Its state is local to one execution context; a real implementation needs the persistence, request and effect contracts described in the trade-offs.

JavaScript · local teaching example

function orderService(notifier) {
  return {ready: id => notifier.send('Order ' + id + ' ready')};
}
const recordingNotifier = {send: message => 'recorded: ' + message};
console.log(orderService(recordingNotifier).ready('O1'));

Expected output

recorded: Order O1 ready

Choose a boundary from the situation

Compare boundary choices

Tests must not send real emails.
A database client has a defined shared lifetime.

Compare the visible consequences. With JavaScript enabled, choose an option in each scenario to assemble your decision record.

This is an exploratory design exercise. It does not run infrastructure, measure capacity or replace the lesson’s quiz.

Choose the responsibility and examine what it does and does not promise.

All alternatives and consequences remain visible. Select one per situation to record your reasoning.

Choose it for a concrete need

Use it when dependency choices or test boundaries need to be explicit. Keep the composition root understandable and define request, process or shared lifetimes deliberately.

An oversized dependency container can hide wiring errors and make failures appear late. A Service Locator asks a global resolver for dependencies, which can obscure what a consumer requires. Injecting an object does not make its effects atomic or safe for every concurrent caller.

Compare the neighbouring responsibilities

Strategy supplies interchangeable behaviour; injection is one way to provide that strategy. A Singleton describes a scoped lifetime and can be injected without giving consumers global access.

Continue with Strategy’s interchangeable contract, shared lifetime and global access, construction extension points.

For primary further reading, use Martin Fowler’s Dependency Injection explanation and compare the responsibilities with this course’s original example.

Check your pattern reasoning

Does Dependency Injection require a framework container?
Choose one answer. You can try again.

Answer and explanation

No; explicit parameters can supply dependencies.

The responsibility is external supply and assembly.

Check your pattern reasoning

Where should supported implementation choices be assembled?
Choose one answer. You can try again.

Answer and explanation

At an explicit composition boundary.

Wiring and lifetime can remain visible.

Keep the boundary and its guarantees explicit

Use Dependency Injection when its responsibility matches the actual problem. Keep implementation lifetime, concurrency and error behaviour visible rather than treating an organizational label as proof.

Return to the design-pattern guide or apply this alongside construction extension points.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Repository Pattern →