ADR-0002. Kiến trúc phân lớp use-case (controller giữ mỏng)
| Trường | Giá trị |
|---|---|
| Status | Accepted |
| Ngày | 2026-04-05 |
| Người quyết định | Phat Nguyen |
| Thay thế | - |
Bối cảnh
- Helpdesk có phạm vi rất rộng: 11 controller và hàng chục thao tác trải dài ticket, agent, SLA, bài viết, khảo sát, yêu cầu tính năng và thông báo.
- Đặt logic nghiệp vụ trong controller (kiểu service phình to thường gặp trong IGNIS) sẽ tạo ra những controller cồng kềnh, và khiến cùng một thao tác khó được gọi từ cả handler HTTP lẫn worker BullMQ (ví dụ
AutoAssignTicketUseCasechạy từ assignment worker, cònRunSlaMonitorUseCasechạy từ SLA worker).
Quyết định
Áp dụng một lớp use-case: mỗi thao tác nghiệp vụ là một lớp đơn trách nhiệm trong src/application/use-cases/<domain>/<verb>.use-case.ts, được đăng ký làm service DI trong configureServices(). Cả controller và worker đều phụ thuộc vào use-case thông qua injection.
- Controller giữ mỏng: kiểm soát truy cập theo merchant (
assertMerchantAccess()+useRequestContext()), ánh xạ DTO, gọi một use-case. Không có decorator@authenticate; việc kiểm soát truy cập thực thi ngay trong handler. - Các service thuần (
PermissionService,CompensationCalculatorService,ProcessNotificationService, v.v.) giữ phần logic xuyên suốt dùng chung mà nhiều use-case cùng tái sử dụng. - Repository chỉ giữ phần truy xuất dữ liệu; schema được tập trung trong
@nx/core.
Hệ quả
| Ưu | Nhược |
|---|---|
| Cùng một thao tác dùng lại được từ cả HTTP và worker (chung một đường code) | Nhiều file nhỏ - số lượng lớp cao, khó điều hướng |
| Tính đơn trách nhiệm giúp mỗi thao tác kiểm thử được một cách độc lập | Danh sách đăng ký DI trong application.ts dài và phải luôn được giữ đồng bộ |
| Controller mỏng; ranh giới rõ ràng giữa tầng truyền tải và logic | Rủi ro use-case bị bỏ rơi (một lời gọi bị comment khiến assignTicketUseCase trở thành code chết - xem mục Vận hành → Vấn đề đã biết) |
Phương án đã cân nhắc
| Tùy chọn | Vì sao bị từ chối |
|---|---|
| Service phình to (một service cho mỗi controller) | Khó chia sẻ một thao tác giữa điểm vào HTTP và worker; service phình to dần |
| Đặt logic trong controller | Không dùng lại được từ worker; vi phạm quy ước dự án về việc tách logic ra khỏi controller |
Tham khảo
src/application/use-cases/**(các lớp use-case)src/application.tsconfigureServices()(đăng ký DI)src/controllers/ticket/ticket.controller.ts(controller mỏng ủy thác cho use-case)AGENTS.md- "Keep business logic in use-cases, not controllers"