DI Containers
DI containers manage object creation, lifecycle, and dependency resolution automatically.
What is DI?
Without DI (Tight Coupling):
┌──────────┐ ┌──────────────┐
│ Order │─────▶│ MySQLDatabase │
│ Service │ │ (concrete) │
└──────────┘ └──────────────┘
- OrderService creates its own dependencies
- Hard to test, hard to change
With DI (Loose Coupling):
┌──────────┐ ┌────────────────┐
│ Order │─────▶│ Database │
│ Service │ │ (interface) │
└──────────┘ └───────┬────────┘
│ implemented by
┌─────┴──────┐
│ │
┌────┴────┐ ┌────┴────┐
│ MySQL │ │Postgres │
└─────────┘ └─────────┘
- OrderService receives dependency externally
- Easy to test, easy to change
DI Container Features
// Spring-style container
@Service
public class OrderService {
private final OrderRepository repository;
private final PaymentService paymentService;
@Autowired
public OrderService(OrderRepository repository, PaymentService paymentService) {
this.repository = repository;
this.paymentService = paymentService;
}
}
// Container resolves dependencies automatically
@Service
public class OrderRepository {
@Autowired
private Database database;
}
// Container manages:
// 1. Object creation
// 2. Dependency resolution
// 3. Lifecycle (singleton, request, etc.)
// 4. Configuration
Container Configuration
// Java-based configuration
@Configuration
public class AppConfig {
@Bean
public Database database() {
return new PostgresDatabase("jdbc:...");
}
@Bean
@Scope("singleton")
public OrderService orderService() {
return new OrderService(database(), paymentService());
}
}
// Or auto-scanning
@ComponentScan(basePackages = "com.myapp")
@Configuration
public class AppConfig { }
Common DI Containers
| Container | Language | Features |
|---|---|---|
| Spring | Java | Full-featured, annotations |
| Guice | Java | Lightweight, module-based |
| Dagger | Java | Compile-time, no reflection |
| Inversify | TypeScript | Decorators, interfaces |
| NestJS | TypeScript | Built-in, module system |
| ASP.NET Core | C# | Built-in, constructor injection |
Constructor Injection
Constructor injection provides dependencies through the constructor, ensuring immutability and clear dependencies.
Implementation
// Immutable class with constructor injection
public class OrderService {
private final OrderRepository repository;
private final PaymentService paymentService;
private final NotificationService notificationService;
// All dependencies injected through constructor
public OrderService(OrderRepository repository,
PaymentService paymentService,
NotificationService notificationService) {
this.repository = Objects.requireNonNull(repository);
this.paymentService = Objects.requireNonNull(paymentService);
this.notificationService = Objects.requireNonNull(notificationService);
}
public Order createOrder(CreateOrderRequest request) {
Order order = Order.fromRequest(request);
paymentService.charge(order);
repository.save(order);
notificationService.sendConfirmation(order);
return order;
}
}
Benefits of Constructor Injection
- Immutability: Dependencies can be final
- Clear dependencies: Constructor shows what's needed
- Validation: Can validate dependencies in constructor
- Testability: Easy to pass mocks
- Required dependencies: Forces all dependencies to be provided
Testing with Constructor Injection
// Easy to test!
public class OrderServiceTest {
@Test
void shouldCreateOrder() {
// Create mocks
OrderRepository mockRepo = mock(OrderRepository.class);
PaymentService mockPayment = mock(PaymentService.class);
NotificationService mockNotify = mock(NotificationService.class);
// Inject mocks
OrderService service = new OrderService(mockRepo, mockPayment, mockNotify);
// Test in isolation
Order order = service.createOrder(testRequest);
// Verify interactions
verify(mockRepo).save(any());
verify(mockPayment).charge(any());
verify(mockNotify).sendConfirmation(any());
}
}
Constructor Injection vs Setter Injection
| Aspect | Constructor | Setter |
|---|---|---|
| Required deps | ✓ | Optional |
| Immutability | ✓ | ✗ |
| Clear deps | ✓ | Less clear |
| Testing | Easy | Easy |
| Optional deps | Use overloaded constructors | ✓ |
| Circular deps | Fails fast | Can cause issues |
Method Injection
Method injection provides dependencies through methods, useful for optional or context-dependent dependencies.
Setter Injection
public class OrderProcessor {
private OrderRepository repository;
private PaymentService paymentService;
// Setter injection for optional dependencies
public void setRepository(OrderRepository repository) {
this.repository = repository;
}
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
// Dependencies can be null if not set
public void process(Order order) {
if (repository != null) {
repository.save(order);
}
}
}
Method Injection
public class ReportGenerator {
// Method injection: dependency provided per method call
public Report generate(DataProvider provider) {
Data data = provider.getData();
return buildReport(data);
}
}
// Different providers for different contexts
Report dailyReport = generator.generate(new DailyDataProvider());
Report monthlyReport = generator.generate(new MonthlyDataProvider());
Field Injection (Anti-Pattern)
@Service
public class OrderService {
@Autowired
private OrderRepository repository; // Anti-pattern!
// Problems:
// - Can't make final
// - Can't validate in constructor
// - Hard to test without container
// - Hidden dependencies
}
Injection Methods Comparison
| Method | Immutability | Testing | Required | Recommended |
|---|---|---|---|---|
| Constructor | ✓ | Easy | ✓ | ✓ Best |
| Setter | ✗ | Easy | Optional | Sometimes |
| Method | ✗ | Easy | Per-call | Rare |
| Field | ✗ | Hard | ✗ | ✗ Avoid |
Best Practices
- Prefer constructor injection for required dependencies
- Use setter injection for optional dependencies
- Avoid field injection — hidden dependencies, hard to test
- Validate dependencies in constructor (null checks)
- Use interfaces for dependencies (DIP)
- Keep constructors small — few dependencies means good design
Practice Problems
Design a scalable Dependency Injection system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Dependency Injection to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Dependency Injection and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What is dependency injection?
2. Which injection method is recommended?
3. Why is field injection an anti-pattern?
4. What does a DI container manage?
5. Why use interfaces for injected dependencies?
Flashcards
Question
What is dependency injection?
Click to reveal answer
Answer
Providing dependencies from outside rather than creating them internally. Enables loose coupling, testability, and flexibility.
Question
Best injection method?
Click to reveal answer
Answer
Constructor injection. Enables immutability, clear dependencies, validation, and easy testing. Use setter for optional deps.
Question
Why avoid field injection?
Click to reveal answer
Answer
Hidden dependencies, can't make final, can't validate in constructor, hard to test without container.
Question
What does a DI container do?
Click to reveal answer
Answer
Manages object creation, resolves dependencies automatically, controls lifecycle (singleton, request), handles configuration.
Question
DI and SOLID principles?
Click to reveal answer
Answer
DI implements DIP (depend on abstractions). Enables SRP (separate concerns). Supports OCP (easy to extend).
Revision Notes
Key Takeaways
- 1.DI provides dependencies from outside, enabling loose coupling
- 2.Constructor injection is recommended for required dependencies
- 3.DI containers manage object creation and dependency resolution
- 4.Field injection is an anti-pattern — hidden dependencies, hard to test
- 5.DI implements Dependency Inversion Principle (DIP) from SOLID
Interview Tips
- •Explain how DI enables loose coupling and testability
- •Show constructor injection in your design
- •Discuss how DI container simplifies dependency management
- •Explain why field injection should be avoided
Cheat Sheet
Dependency Injection - Cheat Sheet
DI = Dependencies from outside, not created internally.
Injection Methods:
| Method | Immutability | Testing | Recommended |
|---|---|---|---|
| Constructor | ✓ | Easy | ✓ Best |
| Setter | ✗ | Easy | Optional |
| Method | ✗ | Easy | Rare |
| Field | ✗ | Hard | ✗ Avoid |
DI Container Features:
- Auto object creation
- Dependency resolution
- Lifecycle management
- Configuration
Common Containers:
Spring, Guice, Dagger, NestJS, ASP.NET
Best Practices:
- Constructor injection for required deps
- Setter for optional
- Avoid field injection
- Use interfaces (DIP)
- Validate in constructor