Skip to content

ADR-0002. Use-case layered architecture (controllers stay thin)

FieldValue
StatusAccepted
Date2026-04-05
Deciderssupport-team
Supersedes

Context

  • Helpdesk has a large surface: 11 controllers and dozens of operations spanning tickets, agents, SLA, articles, surveys, feature requests, and notifications.
  • Putting business logic in controllers (the common IGNIS pattern of fat services) would produce sprawling controllers and make the same operation hard to invoke from both an HTTP handler and a BullMQ worker (e.g. AutoAssignTicketUseCase runs from the assignment worker, RunSlaMonitorUseCase from the SLA worker).

Decision

Adopt a use-case layer: each business operation is a single-responsibility class in src/application/use-cases/<domain>/<verb>.use-case.ts, registered as a DI service in configureServices(). Controllers and workers both depend on use-cases via injection.

  • Controllers are thin: enforce per-merchant access (assertMerchantAccess() + useRequestContext()), map DTOs, call a use-case. No @authenticate decorator; access is enforced in the handler.
  • Plain services (PermissionService, CompensationCalculatorService, ProcessNotificationService, etc.) hold shared cross-cutting logic that multiple use-cases reuse.
  • Repositories hold only data access; schemas are centralized in @nx/core.

Consequences

ProsCons
Same operation reusable from HTTP and workers (one code path)Many small files — high class count to navigate
Single responsibility makes each operation testable in isolationDI registration list in application.ts is long and must stay in sync
Thin controllers; clear boundary between transport and logicRisk of orphaned use-cases (a commented-out call left assignTicketUseCase dead — see Operations Known Issues)

Alternatives Considered

OptionWhy rejected
Fat services (one service per controller)Hard to share an operation between HTTP and worker entry points; services balloon
Logic in controllersNot reusable from workers; violates project guidance to keep logic out of controllers

References

  • src/application/use-cases/** (use-case classes)
  • src/application.ts configureServices() (DI registration)
  • src/controllers/ticket/ticket.controller.ts (thin controller delegating to use-cases)
  • AGENTS.md — "Keep business logic in use-cases, not controllers"

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