Catalog
concept#Software Engineering#Architecture#Integration

Dependency Injection

Design pattern that decouples components by providing their dependencies from the outside, improving testability and modularity.

Dependency injection is a design pattern that decouples components by providing their dependencies from the outside.
Established
Medium

Classification

  • Medium
  • Technical
  • Architectural
  • Intermediate

Technical context

Frameworks such as Spring, Guice or .NET DIConfiguration systems (environment, config server)Test frameworks for mock and stubbing support

Principles & goals

Declare dependencies explicitlyPrefer constructor injectionSeparate configuration from runtime binding
Build
Team, Domain

Use cases & scenarios

Compromises

  • Excessive use leads to hard-to-follow runtime configurations
  • Incorrect lifecycle binding can cause resource leaks
  • Hidden dependencies when dependencies are not clearly documented
  • Prefer constructor injection for required dependencies
  • Document scopes and lifecycles clearly
  • Avoid using Service Locator pattern as a substitute

I/O & resources

  • Abstracted interfaces and contracts
  • Selection of an injector or container
  • Convention or configuration guidelines
  • Encapsulated, testable components
  • Central configuration points for implementation binding
  • Documented dependency graphs

Description

Dependency injection is a design pattern that decouples components by providing their dependencies from the outside. It improves testability, modularity and reuse by separating object creation and configuration from business logic. Lifecycle management, dependency scopes and increased indirection should be considered when applying the pattern.

  • Increased testability by easy swap of dependencies
  • Reduced coupling and clearer module boundaries
  • Better reuse and interchangeability of implementations

  • Increased complexity due to added indirection
  • Lifecycle management can become challenging
  • Not every dependency is suitable for injection (e.g., trivial data access)

  • Share of injected dependencies

    Percentage of dependencies provided via DI instead of being created directly.

  • Test coverage of isolated components

    Measure of unit test coverage for components isolated via DI.

  • Configuration errors per release

    Number of runtime errors caused by incorrect DI bindings.

Constructor injection in a service

A service receives repository and logger dependencies via constructor, allowing unit tests with mocks.

Spring profiles for environment variants

Different beans are loaded depending on active profile to separate test and production implementations.

Policy-based injection for feature toggles

Implementations are bound based on feature flags or policies to control experimental functionality.

1

Define interfaces and make dependencies explicit

2

Choose an injection approach (e.g., constructor injection)

3

Integrate container/framework and configure bindings

4

Introduce tests and migration path incrementally

⚠️ Technical debt & bottlenecks

  • Outdated bindings that are no longer tested
  • Proliferating configuration files with inconsistent bindings
  • Unclear ownership of bindings across teams
Lifecycle managementConfiguration complexityInitialization time
  • Injecting primitive values instead of configuration objects
  • Injecting too many responsibilities into a constructor
  • Dynamic bindings in production without testing
  • Unclear scope definitions lead to unexpected instance lifetimes
  • Late error detection due to runtime-based bindings
  • Hidden side effects from injected singleton dependencies
Understanding of interfaces and abstractionsKnowledge of DI principles and container operationExperience with lifecycle and scope management
TestabilityModularityReusability
  • Existing legacy APIs without interfaces hinder injection
  • Constraints from runtime environment (e.g., limited reflection)
  • Organizational conventions for dependency management must be established