Dependency injection pattern
Class structure for injectable dependencies and container wiring.
Dependency injection is a cornerstone of testable, maintainable architecture, and this diagram lays out the class structure that makes it work: a container that registers and resolves instances, the business logic layers that receive their dependencies instead of building them, and the flow of dependencies from the container down through service and controller tiers.
The pattern separates construction (the container's job) from usage (the service's job), so you can test UserService in isolation by injecting a mock repository, and you can swap implementations without touching the service code.
When to use this template
- Architecture design sessions — align your team on which classes need which dependencies before you write factories or decorators.
- Onboarding new developers — this diagram answers "why do we pass the repository to the service instead of letting the service create it?" and shows the full object graph at a glance.
- Refactoring tightly coupled code — if you find classes creating their dependencies internally, this template is your refactoring roadmap.
How to adapt it
Start by identifying your application's layers and which classes live in each:
- Replace Container, Repository, Service, and Controller with your actual classes (e.g., AppModule, ProductRepository, ProductService, ProductController).
- Add intermediate layers like mappers, validators, or handlers between the controller and service if your domain needs them.
- If your framework uses decorators or annotations (Spring @Autowired, NestJS @Inject), add a note in the Controller or Service class listing the injection point.
Visual edits regenerate clean Mermaid code, so you can drag classes around and adjust arrows without touching syntax — ideal for collaborating on your architecture before you commit to it.
Mermaid code
Copy it anywhere Mermaid is supported — GitHub, Notion, or your docs.
classDiagram
class Container {
register(key, instance)
resolve(key)
}
class UserRepository {
getById(id)
save(user)
}
class UserService {
-repository UserRepository
getUser(id)
updateUser(user)
}
class Controller {
-service UserService
handleGetUser(req)
}
Container --> UserRepository
Container --> UserService
UserService --> UserRepository
Controller --> UserService
Frequently asked questions
- What is dependency injection?
- Dependency injection is a design pattern where a class receives its dependencies (like a database repository or logger) rather than creating them internally. Instead of new UserService creating its own UserRepository, the DI container constructs UserRepository and passes it to UserService. This makes code testable (you can inject a mock repository) and loosely coupled (the service doesn't know how the repository is built).
- What does this class diagram show?
- It shows the relationships in a dependency-injection setup: the Container at the top creates and wires instances, the UserRepository handles data access, UserService uses that repository to implement business logic, and the Controller uses the service to handle HTTP requests. Arrows point from dependent to dependency — Controller depends on UserService, UserService depends on UserRepository.
- How do I adapt this for my own application?
- Rename the classes to match your domain — instead of UserService, use OrderService; instead of UserRepository, use ProductRepository. Add more layers (middleware, mappers, validators) as needed. Keep the container at the root, and point dependencies upward; if you see a circular arrow, you likely have a coupling problem to refactor.
- What languages and frameworks support dependency injection?
- Most modern frameworks do: Spring and Guice (Java), .NET's DependencyInjection (C#), NestJS and InversifyJS (Node.js/TypeScript), Laravel (PHP). Many also support it declaratively — decorators (@Inject in NestJS) or annotations (Spring @Autowired) — so the container wires dependencies without explicit register/resolve calls.