principletypescriptMajor
Dependency Injection: Receive Dependencies, Don't Create Them
Viewed 0 times
dependency injectioninversion of controlconstructor injectiontestabilitycomposition rootdi container
Problem
A class that instantiates its own dependencies with
new is tightly coupled to those implementations. It is untestable without running the real infrastructure and closed to substitution.Solution
Accept dependencies as constructor parameters typed against interfaces. The caller (or a DI container) supplies the concrete implementation. The class never calls
new on its dependencies.// Bad: tightly coupled
class OrderService {
private repo = new PostgresOrderRepository(); // hardcoded
async getOrder(id: string) { return this.repo.findById(id); }
}
// Good: dependency injected
class OrderService {
constructor(private repo: OrderRepository) {} // interface type
async getOrder(id: string) { return this.repo.findById(id); }
}
// In production
const service = new OrderService(new PostgresOrderRepository(prisma));
// In tests
const service = new OrderService(new InMemoryOrderRepository());Why
DI inverts control: the class declares what it needs; external code decides what to provide. This makes classes independently testable and makes swapping implementations trivially safe.
Gotchas
- Constructor injection is preferred over property injection — it makes dependencies explicit and required rather than optional and potentially undefined.
- DI containers (tsyringe, inversify, NestJS IoC) automate wiring but add complexity. Manual DI at the composition root is sufficient for many applications.
- Avoid injecting the DI container itself into classes — that is the Service Locator anti-pattern.
Revisions (0)
No revisions yet.