Skip to content

ADR-0001: Layered Code Organised Around Clean Architecture

Field Value
Status Accepted
Date 2026-01-15
Decision owners @dichit-team
Related ADRs none

Context

The backend grew from a prototype into a production service handling auctions, payments, and notifications. We needed predictable structure so that:

  • Business logic could be unit-tested without real infrastructure.
  • Frameworks/providers (Prisma, Redis, AWS, Firebase) could be swapped without touching callers.
  • New engineers could reason about "where does this belong?" consistently.

Constraints: TypeScript strict, a single deployable service, few engineers initially, and a strong preference for YAGNI.

Alternatively we considered organising strictly by feature (vertical slices) or by framework, but early features (users, auctions, payments) shared repositories and providers, making strict vertical slices awkward.

Decision

Organise src/ into five layers that follow the Dependency Rule — source dependencies point inward:

domain  ←  application  ←  (application/interfaces)  ←  infrastructure
  • domain/ — entities + helpers, no I/O.
  • application/ — use cases/services/queue processors + dependency contracts.
  • infrastructure/ — concrete implementations (repositories, providers, config).
  • interfaces/ — Fastify boot, routes, controllers, webhooks, websocket.
  • shared/ — cross-cutting constants, errors, zod/swagger schemas.

Wiring is done with Awilix in a composition root (src/interfaces/app.ts + src/infrastructure/di.ts) using constructor injection. See clean-architecture.md.

What

Positive:

  • Testable use cases (inject contract fakes).
  • Swappable providers (payment adapter zwitch vs mock; see overview.md).
  • Clear import rules reviewers can enforce.

Negative / costs:

  • More files per feature; interface ceremony.
  • Risk of over-abstraction for trivial read paths.

Mitigations:

  • Prefer the smallest coherent slice per feature (see folder-structure.md).
  • Repository contracts reflect domain entities, not framework rows.

Alternatives Considered

Vertical slices (feature-first). Great for cohesion, but duplicated plumbing for auth/queues/providers across slices; rejected for the early team size.

Single "service" layer (JAM). Faster initially, but business logic leaked into controllers and providers; rejected for lack of testability.

Dependency-free modules. Avoided; the team already relies on Awilix and no external framework was introduced (rule: never add libraries unless requested).

Status

Accepted. Living documentation of how teams implement new features is in development/folder-structure.md and dependency-injection.md.