Files
2026-01-11 13:04:33 +01:00

4.4 KiB

Delivery Adapters vs App Code (Strict Clean Architecture)

This document clarifies where Controllers live, why they are adapters, and how to structure them cleanly in large systems.

It resolves the common confusion between: • architectural role (what something is) • physical placement (where something lives in the repo)

This document is framework-agnostic in its principles and feature-based in its structure.

  1. Clean Architecture Layers (Authoritative)

Clean Architecture defines roles, not folders.

ENTITIES USE CASES INTERFACE ADAPTERS FRAMEWORKS & DRIVERS

Everything that follows maps code to these roles.

  1. What a Controller Is (Architecturally)

A Controller is an Interface Adapter.

Why: • It receives external input (HTTP) • It translates that input into Use Case input • It invokes a Use Case • It performs no business logic

By definition:

Anything that translates between external input and the application boundary is an adapter.

So:

Controllers are Interface Adapters

  1. Why Controllers Do NOT Live in adapters/

Although Controllers are adapters by role, they are also: • Framework-specific • Delivery-specific • Not reusable outside their app

They: • use routing decorators • depend on HTTP concepts • depend on a specific framework

Therefore:

Controllers belong to the delivery application, not to shared adapters.

  1. Adapter vs App — The Key Distinction

Concept Meaning Adapter Architectural role (translator) App Delivery mechanism (HTTP, UI, CLI)

“Adapter” answers what it does. “App” answers where it runs.

Both are correct at the same time.

  1. Feature-Based Structure (Mandatory for Scale)

Flat technical folders do not scale.

Everything is organized by feature / bounded context.

  1. Canonical Project Structure (Strict)

Root

core/ # Application + Domain (pure) adapters/ # Reusable infrastructure adapters apps/ # Delivery applications

Core (Feature-Based)

core/ └── racing/ ├── domain/ │ ├── entities/ │ ├── value-objects/ │ └── services/ └── application/ ├── use-cases/ ├── inputs/ ├── results/ └── ports/ ├── gateways/ └── output/

•	No framework imports
•	No DTOs
•	No controllers

Reusable Adapters (Framework-Agnostic Implementations)

adapters/ └── racing/ ├── persistence/ │ ├── TypeOrmRaceRepository.ts │ └── TypeOrmDriverRepository.ts ├── presentation/ │ └── presenters/ │ └── GetDashboardOverviewPresenter.ts ├── messaging/ │ └── EventPublisher.ts └── logging/ └── StructuredLogger.ts

These adapters: • implement Core ports • are reusable across apps • do not depend on routing or UI

API App (Delivery Application)

apps/api/ └── racing/ ├── controllers/ │ └── DashboardController.ts ├── services/ │ └── DashboardApplicationService.ts ├── dto/ │ └── DashboardOverviewResponseDto.ts └── module.ts

Responsibilities: • Controllers translate HTTP → Input • Application Services orchestrate Use Case + Presenter • DTOs represent HTTP contracts • Module wires dependencies

  1. Responsibilities by Layer (No Overlap)

Controllers • HTTP only • No business logic • No mapping logic • Call Application Services only

Application Services (API) • Instantiate Output Adapters • Invoke Use Cases • Return response DTOs

Presenters • Implement Output Ports • Map Result → DTO/ViewModel • Hold state per execution

  1. Forbidden Patterns

Controllers inside adapters/ Use Cases inside apps/api DTOs inside core Controllers calling Use Cases directly Business logic in Controllers or Services

  1. Final Mental Model

Controllers are adapters by responsibility. Apps define where adapters live.

This separation allows: • strict Clean Architecture • multiple delivery mechanisms • feature-level scalability

  1. One-Line Summary

Controller = Adapter (role), App = Delivery (location).

This document is the authoritative reference for controller placement and adapter roles.