Không. Điểm cốt lõi của DIP là ai sở hữu interface, chứ không phải việc có tồn tại interface hay không.
DIP nói: module cấp cao và module cấp thấp đều phụ thuộc vào abstraction, và abstraction do phía cấp cao định nghĩa. Nếu bạn tách IEmailSender nhưng đặt nó cạnh lớp SMTP và mô tả theo ngôn ngữ SMTP, hướng phụ thuộc vẫn chưa đảo — domain vẫn phải biết về hạ tầng.
Cách kiểm tra thực tế: domain có import gì từ tầng hạ tầng không?
domain/ order.ts, notifier.ts (interface) ← không import ra ngoài
infra/ smtp-notifier.ts implements Notifier ← import vào domain
app/ main.ts nối hai bên lạiMũi tên phụ thuộc lúc này chỉ đi vào trong. Đổi SMTP sang một dịch vụ khác chỉ động vào infra/.
Ba dấu hiệu chưa thực sự đảo phụ thuộc:
- Interface có method mang tên kỹ thuật của hạ tầng (executeSql, publishToKafka) thay vì ý định nghiệp vụ (save, notifyPaid).
- Kiểu dữ liệu của ORM, HTTP response hay driver xuất hiện trong chữ ký interface.
- Interface nằm cùng package với implementation và chỉ có đúng một implementation phục vụ mock.
Và mặt trái: DIP có chi phí — thêm lớp gián tiếp, khó lần vết hơn. Áp cho ranh giới có khả năng thay đổi hoặc cần cô lập khi test (DB, cổng thanh toán, mail, đồng hồ hệ thống). Đừng dựng interface cho mọi class chỉ để tuân thủ hình thức.