Explore dependency injection in Scala through constructor wiring, capability traits, and explicit context, without defaulting to container-heavy ceremony.
Dependency injection in Scala: Constructing components by supplying their collaborators explicitly instead of having them discover or create dependencies internally.
Dependency injection matters in Scala for the same reason it matters anywhere else: it keeps runtime assembly separate from business behavior. What changes is that Scala often achieves DI with much less framework machinery than classic enterprise examples suggest.
Most of the time, Scala DI starts here:
1final class BillingService(
2 payments: PaymentGateway,
3 invoices: InvoiceRepository,
4 clock: Clock
5)
This is simple, honest, and testable. The service declares what it needs, and assembly happens elsewhere. That already solves most of the design problem.
Scala traits are a natural fit for DI because they provide small capability boundaries:
This keeps substitution and testing clean without forcing every component into a heavyweight framework.
The cake pattern is part of Scala DI history, but many teams now avoid it for ordinary application code because:
It is an interesting technique and still occasionally useful, but constructor-based wiring is usually easier to teach and maintain.
In functional code, DI may look like:
The important idea is still the same: do not let the domain logic secretly reach out for the world. Make capabilities available deliberately.
A DI container can help in large applications, but it is not the essence of the pattern. The anti-pattern is assuming that “proper DI” requires a framework even when manual composition would be clearer.
Small and medium Scala services often benefit from:
rather than container indirection.
If readers cannot tell where dependencies come from, the design may be more magical than decoupled.
A clever composition technique becomes a maintenance burden.
If a component needs a long list of microscopic dependencies, the deeper design may need restructuring.
Prefer constructor injection and small capability traits first. Keep assembly explicit, use frameworks only when they genuinely reduce complexity, and judge DI success by boundary clarity and testability rather than by the sophistication of the wiring mechanism.