Bản thân "một instance duy nhất" không sai — vấn đề nằm ở cách Singleton cổ điển tự quản lý instance và được gọi thẳng từ mọi nơi.
Hệ quả:
- Phụ thuộc ẩn. OrderService gọi Database.getInstance() bên trong, nên chữ ký hàm không hề cho biết nó cần DB. Đọc constructor không đủ hiểu service phụ thuộc gì.
- Khó thay thế khi test. Muốn test với DB giả thì phải chọc vào biến static toàn cục hoặc dùng cơ chế mock module.
- Trạng thái rò rỉ giữa các test. Instance sống suốt process nên test này ảnh hưởng test kia, gây flaky theo thứ tự chạy.
- Vi phạm DIP. Code phụ thuộc trực tiếp vào lớp cụ thể chứ không phải abstraction.
Cách làm thay thế: vẫn tạo đúng một instance ở tầng khởi động (composition root), nhưng truyền vào nơi cần thay vì để nơi đó tự lấy.
const db = createDb(env.DATABASE_URL) // tạo một lần khi boot
const orders = new OrderService(db) // phụ thuộc lộ rõ, test thay đượcBạn giữ được tính duy nhất mà không mất khả năng thay thế.
Singleton "thật" chỉ còn hợp lý cho những thứ vô hại và không trạng thái nghiệp vụ, ví dụ logger hay bộ đọc config chỉ đọc.