ADR-0002. Use-case layered architecture (controllers stay thin)
| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-04-05 |
| Deciders | support-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.
AutoAssignTicketUseCaseruns from the assignment worker,RunSlaMonitorUseCasefrom 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@authenticatedecorator; 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
| Pros | Cons |
|---|---|
| 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 isolation | DI registration list in application.ts is long and must stay in sync |
| Thin controllers; clear boundary between transport and logic | Risk of orphaned use-cases (a commented-out call left assignTicketUseCase dead — see Operations Known Issues) |
Alternatives Considered
| Option | Why rejected |
|---|---|
| Fat services (one service per controller) | Hard to share an operation between HTTP and worker entry points; services balloon |
| Logic in controllers | Not reusable from workers; violates project guidance to keep logic out of controllers |
References
src/application/use-cases/**(use-case classes)src/application.tsconfigureServices()(DI registration)src/controllers/ticket/ticket.controller.ts(thin controller delegating to use-cases)AGENTS.md— "Keep business logic in use-cases, not controllers"