#Dependency Injection (DI)
“Đừng tự tạo dependency bên trong class – hãy nhận nó từ bên ngoài”
DI là kỹ thuật triển khai của DIP. Nếu DIP là nguyên lý thiết kế, thì DI là cách hiện thực hoá nguyên lý đó trong code.
#1. Bản chất của DI
#1.1 Inversion ở mức object graph
Không phải class tự new dependency:
class A {
private B $b;
public function __construct() {
$this->b = new B();
}
}
Mà dependency được cung cấp từ bên ngoài:
class A {
public function __construct(private B $b) {}
}
👉 Quyền kiểm soát việc tạo object được chuyển ra ngoài (composition root)
#1.2 Object graph
Một hệ thống thực tế là một đồ thị object:
Controller → Service → Repository → DB
DI giúp:
- Build graph này ở 1 nơi duy nhất
- Quản lý lifecycle
- Thay đổi implementation mà không đổi business logic
#2. Các loại Dependency Injection
#2.1 Constructor Injection
class UserService {
public function __construct(
private UserRepository $repo
) {}
}
Ưu điểm:
- Immutable dependency
- Rõ ràng
- Dễ test
#2.2 Method Injection
class ReportService {
public function generate(Logger $logger) {}
}
Use case: dependency optional hoặc chỉ dùng 1 lần
#2.3 Property Injection (không khuyến khích)
class A {
public B $b;
}
Vấn đề:
- Không đảm bảo initialized
- Khó trace
#3. Composition Root (khái niệm cực quan trọng)
Nơi duy nhất khởi tạo và wiring toàn bộ dependency
#3.1 Sai lầm phổ biến
- new object rải rác khắp code
#3.2 Đúng
function bootstrap(): App {
$db = new MySQLConnection();
$repo = new UserRepository($db);
$service = new UserService($repo);
return new App($service);
}
#4. DI Container
#4.1 Vai trò
- Resolve dependency
- Manage lifecycle
- Auto wiring
#4.2 Tự build container đơn giản
class Container {
private array $bindings = [];
public function bind(string $abstract, callable $factory) {
$this->bindings[$abstract] = $factory;
}
public function make(string $abstract) {
return $this->bindings[$abstract]($this);
}
}
#5. Laravel DI Container (thực chiến)
#5.1 Auto resolve
class UserService {
public function __construct(UserRepository $repo) {}
}
Laravel tự resolve nếu class có thể new được
#5.2 Bind interface
$this->app->bind(
UserRepository::class,
EloquentUserRepository::class
);
#5.3 Singleton vs Bind
$this->app->singleton(Logger::class, function () {
return new Logger();
});
| Type | Behavior |
|---|---|
| bind | new instance mỗi lần |
| singleton | shared instance |
#5.4 Contextual binding
$this->app->when(AdminService::class)
->needs(Logger::class)
->give(AdminLogger::class);
#6. Lifecycle & Scope
#6.1 Singleton
- DB connection
- Logger
#6.2 Transient
- Service nhẹ
#6.3 Scoped (request)
- HTTP request lifecycle
👉 Sai scope = bug khó debug
#7. Anti-patterns
#7.1 Service Locator
app()->make(UserService::class);
👉 Hidden dependency
#7.2 Over-injection
Constructor 10+ dependencies → smell
#7.3 God container
- Bind mọi thứ
- Khó maintain
#8. DI + Testing
#8.1 Mock dễ dàng
$service = new UserService(
new FakeUserRepository()
);
#8.2 Isolation
- Không cần DB thật
- Test nhanh
#9. DI trong kiến trúc lớn
#9.1 Clean Architecture
- Outer layer inject vào inner
#9.2 Hexagonal
- Adapter inject vào Port
#9.3 Microservices
- Inject client (HTTP, gRPC)
#10. Performance & Trade-off
#Ưu
- Flexible
- Testable
#Nhược
- Indirection
- Debug khó hơn
#11. Khi nào KHÔNG dùng DI
- Script nhỏ
- Không có dependency
#12. Best Practices
- Constructor injection first
- Interface khi cần
- Keep constructor nhỏ
- Group dependency (Facade/Service)
#13. Interview Questions
DI là gì?
Summary:
- Inject dependency từ ngoài
Deep:
- Control object creation
DI khác DIP?
Summary:
- DI là technique
Deep:
- DIP là principle
Khi nào dùng singleton?
Summary:
- Shared resource
Deep:
- Tránh tạo nhiều instance
Service Locator là gì?
Summary:
- Lấy dependency từ container
Deep:
- Hidden dependency → anti-pattern
#14. Kết luận
DI là nền tảng để:
- Viết code testable
- Build architecture scalable
👉 Junior: biết dùng DI 👉 Senior: hiểu lifecycle + container 👉 Architect: thiết kế object graph + boundary