Dependency Injection quy mô lớn
Code lớn dần, dependency tree phình to. Chương này bàn về chiến lược DI khi app có nhiều module và hàng trăm class.
1. Cạm bẫy God Container
Phần tiêu đề “1. Cạm bẫy God Container”Cách phổ biến: gom tất cả dependency vào một object khổng lồ:
struct AppContext { let api: API let user: User let store: Store let analytics: Analytics // ... 50+ properties}Vấn đề:
- God Object: Mọi class đều phụ thuộc vào
AppContext. - Khó biết class cần gì: Nhìn
init(context: AppContext)— class này dùngapihaystorehay cả hai? - Global state trá hình: Thực chất không khác Singleton.
2. Chia nhỏ dependency tree
Phần tiêu đề “2. Chia nhỏ dependency tree”Thay vì một tree khổng lồ, hãy chia thành sub-trees và mỗi phần tự quản lý:
Ví dụ
Phần tiêu đề “Ví dụ”Phần Settings có mục Payment. Payment cần API và Store:
class Settings { private let user: User private let store: Store private let api: API
func setupMonetization() -> Monetization { let paymentFactory = PaymentSettingsFactory(api: api) return Monetization(user: user, store: store, factory: paymentFactory) }}Settings đóng vai trò composition root cục bộ — quản lý dependency cho sub-tree của nó.
3. Nhiều composition root
Phần tiêu đề “3. Nhiều composition root”Trong app lớn, không thể khởi tạo tất cả dependency tại Main. Thay vào đó, có nhiều composition root rải rác:
- Main: Khởi tạo core dependency (API, Store, Auth).
- TabBar: Khởi tạo các feature chính (Home, Search, Settings).
- Settings: Khởi tạo các sub-features (Payment, Profile, Notifications).
Mỗi điểm là một Factory hoặc Container cục bộ.
4. DI khi chia module
Phần tiêu đề “4. DI khi chia module”Khi tách module, cần quan tâm đến public interface giữa các module:
import FeatureCourseimport CoreNetworking
// App là "người nối dây" giữa các modulelet network = ProductionNetwork() // CoreNetworkinglet api = API(network: network) // CoreNetworkinglet courseAPI = CourseAPI(api: api) // FeatureCourseVấn đề: CourseAPI (feature module) yêu cầu API (networking module) → feature phụ thuộc vào networking.
Giải pháp:
- Dùng Protocol (interface) đặt trong feature module → dependency inversion.
- Hoặc chấp nhận dependency nếu module đó là shared utilities cho toàn app.
Nguyên tắc: Giữ public interface giữa các module nhỏ gọn và rõ ràng.
Kết luận: Ở quy mô lớn: tránh God Container, chia nhỏ dependency tree thành sub-trees, tạo nhiều composition root cục bộ. Khi chia module, dùng Protocol để invert dependency.