Skip to content

ADR-0002. Kiến trúc phân lớp use-case (controller giữ mỏng)

TrườngGiá trị
StatusAccepted
Ngày2026-04-05
Người quyết địnhPhat 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ụ AutoAssignTicketUseCase chạy từ assignment worker, còn RunSlaMonitorUseCase chạ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ả

ƯuNhượ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ậpDanh 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à logicRủ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ọnVì 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 controllerKhô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.ts configureServices() (đă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"

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.