Skip to content

Clean Architecture

dichit-backend is organised around Clean Architecture: source-code dependencies point inward, toward the domain, and never outward. This is the single most important constraint in the codebase — it is enforced by review, not by a compiler rule (see coding-standards).

The Dependency Rule

flowchart TB
    subgraph Outer[Outer layers - frameworks & adapters]
        IF["interfaces/<br/>Fastify, routes, WS, webhooks"]
        INF["infrastructure/<br/>Prisma, Redis, AWS, queue, auth"]
    end

    subgraph Mid[Application - use cases & contracts]
        APP["application/<br/>use-cases, services, queue processors"]
        IF2["application/interfaces/<br/>dependency contracts (repositories, services)"]
    end

    subgraph Inner[Domain]
        DOM["domain/<br/>entities + helpers"]
    end

    IF --> APP
    INF --> IF2
    APP --> DOM
    IF2 -. "implemented by" .-> INF

    style Inner fill:#e8f5e9

Dependency direction is the inverse of execution flow: controllers call use cases, use cases call interfaces (src/application/interfaces/), and the concrete implementations in src/infrastructure/ satisfy those interfaces at runtime via Awilix (see dependency-flow.md and dependency-injection).

Layer responsibilities

Layer Owns Never does
domain/ Entities, enums, value logic No IO, no framework imports
application/ Orchestration, business rules, contracts Talks directly to Prisma/AWS/Redis
infrastructure/ Repositories, providers, config, auth, queue Contains business rules
interfaces/ Routing, validation wiring, controllers, WS Contains business logic
shared/ Cross-cutting constants, errors, zod/swagger schemas

Rules of thumb (from AGENTS.md)

  • Never use any.
  • Never put business logic inside controllers.
  • Never call Prisma directly inside controllers.
  • Never introduce new libraries unless explicitly requested.
  • Never refactor unrelated files.
  • If unsure where code belongs, prefer application/.

Dependency flow in practice

flowchart LR
    C["Controller (interfaces)"] --> UC["Use case (application)"]
    UC --> RepoI["Repository interface (application/interfaces)"]
    UC --> SvcI["Service interface (application/interfaces)"]
    RepoI --> Repo["PrismaRepository (infrastructure)"]
    Repo --> PG[("PostgreSQL")]
    SvcI --> Svc["ProviderService (infrastructure)"]
    Svc --> EXT["S3 / Redis / FCM / SMS"]

Consequences

Good: testable use cases, swappable providers, minimal framework leakage, predictable feature layering.

Costs: more files per feature, interface ceremony, and the discipline of reviewing imports. The payoff is that features like a repository or a payment adapter can change without touching callers.