SuperAPI AI Directory

clean-architecture

General & other

View repository

Updated 10-15 20:31

About this skill

Clean Architecture (and hexagonal / onion)

The golden rule: dependencies point inward. The domain does not know HTTP, SQL, framework. Clean, hexagonal, and onion describe the same design with different vocabularies.

DDD fills the center: ddd. Modules: modular-monolith. UI: mvvm-architecture (View/ViewModel are presentation adapters).

Map of names

Name Emphasis
Clean Rings: Enterprise → Application → Interface adapters → Frameworks
Hexagonal Ports (domain API) and adapters (HTTP, DB, bus)
Onion Domain at the core; infra in the shell

Do not implement the three in parallel folders. Choose the names of the repo.

Rings

┌─────────────────────────────────────┐
│  Frameworks & drivers (HTTP, DB)    │
│  ┌───────────────────────────────┐  │
│  │ Interface adapters            │  │
│  │ (controllers, presenters,     │  │
│  │  repository impl, gateways)   │  │
│  │  ┌─────────────────────────┐  │  │
│  │  │ Application (use cases) │  │  │
│  │  │  ┌───────────────────┐  │  │  │
│  │  │  │ Domain / entities │  │  │  │
│  │  │  └───────────────────┘  │  │  │
│  │  └─────────────────────────┘  │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘
  • Domain: entities, VOs, invariants. No import from web/ORM.
  • Application: use cases (one command / one transaction). Orchestrates ports.
  • Adapters: REST, GraphQL, CLI, UI, sqlx/EF/Prisma, queues.
  • Framework: the host (Gin, Axum, ASP.NET, Nest) only in the outer ring.

Arrow: adapter → application → domain. Never domain → Prisma/Gin.

Ports

  • Output port: trait / interface OrderRepository, Clock, EventPublisher.
  • Input port: the use case (PlaceOrder) called by the controller.
  • One adapter per port and technology. Do not mix SQL into the use case “just this once”.
HTTP handler → PlaceOrder (app) → Order (domain)
                    ↓
              OrderRepository (port) ← Postgres adapter

Use cases

  • Intention name (PlaceOrder, CancelShipment), not OrderService.do.
  • Application input/output ≠ database row ≠ JSON DTO if that leaks infra. Map at the edges.
  • Transaction in the application (or in the repository impl), not in the controller.
  • Authz: the use case requires identity; the ownership rule can live in the domain — authentication-authorization.

Tests

  • Use cases with fake ports: the value of the architecture.
  • Adapters: integration tests (DB, HTTP) separately.
  • If nobody tests the use case without Docker, the arrows probably point the wrong way.

When not to apply

  • Script, 50-line lambda, generated CRUD.
  • Do not create 8 .NET projects (*.Domain.Abstractions.Core) for two screens.
  • Vertical slice inside a module can glue adapter+use case of the feature without global rings — even so the domain does not import the framework. See modular-monolith.

Anti-patterns

  • Entity Framework / Prisma in the “domain”
  • Use case that returns HttpResponse / gin.Context
  • Interface for every class (IOrder + Order) with no second implementation
  • Single DTO crossing all rings
  • “Clean” only in the folder name

Integration with other skills

  • Decision: software-architecture
  • Rich model: ddd
  • Modules / slices: modular-monolith
  • CQRS (read vs write port): cqrs
  • HTTP: api-design + stack (aspnet-core, golang-apis, axum, nestjs-apis)
  • Persistence: stays adapter (entity-framework, sqlx, golang-sqlc, database-prisma)

Completion criteria

  • Domain/application compile without web/ORM packages
  • Use cases testable with fakes
  • Mapping at the edges (HTTP/DB)
  • One naming convention (Clean or ports) consistent in the repo
  • No extra rings without testing/evolution benefit