Clean Architecture — Cheat Sheet

Tag: architecture, software-design, clean-architecture, ddd, solid, backend

Livello: intermedio

TL;DR: Separazione del core di dominio da framework, UI e database tramite la Dependency Rule: le dipendenze puntano sempre verso l'interno.

Concetti chiave

La Dependency Rule impone che il codice interno non conosca nulla del codice esterno.

Entities rappresentano le regole di business enterprise e i modelli di dominio.

Use Cases incapsulano le regole di business specifiche dell'applicazione.

Interface Adapters convertono i dati tra casi d'uso ed elementi esterni (controller, presenter, gateway).

Frameworks & Drivers contengono strumenti concreti: database, web framework, librerie UI, dispositivi.

Il Dependency Inversion Principle (DIP) permette al core di definire interfacce implementate dall'esterno.

Il dominio rimane indipendente da framework web, database relazionali o UI.

Gli Use Cases comunicano con l'esterno usando DTO (Data Transfer Object) disaccoppiati.

Elevata testabilità del core aziendale senza dipendenze da mock complessi o infrastruttura.

Domande tipiche e risposte pronte

Q: Come viene gestita la Dependency Rule quando un Use Case deve salvare un dato su database?R: Definisco un'interfaccia nel livello Use Case (porta/repository) e la implemento nel layer infrastruttura esterno tramite Dependency Inversion.

Q: Qual è la differenza sostanziale tra Entities e Use Cases?R: Le Entities incapsulano la business logic generale applicabile all'azienda, gli Use Cases orchestrano il flusso applicativo specifico dell'operazione.

Q: Perché evitare di passare le Entity direttamente a controller o viste?R: Rompe l'incapsulamento del dominio; uso DTO o ViewModels dedicati per separare il contratto esterno dalla logica interna.

Insidie e errori comuni

Importare package o moduli di infrastruttura (es. ORM, Express, driver DB) all'interno di entities o use case.

Anemic Domain Model: spostare tutta la logica negli use cases riducendo le entity a meri contenitori dati.

Riutilizzare le classi delle entità del database (modelli ORM) direttamente come entità di dominio.

Creare interfacce nel livello esterno invece che nel livello interno che ne consuma il contratto.

Accoppiare i DTO di richiesta/risposta dei casi d'uso direttamente alle librerie di serializzazione (es. annotazioni JSON/framework).

Snippet e comandiTypeScript

// 1. Core Domain (Entities)
export class User {
  constructor(public readonly id: string, public email: string) {}
  hasValidEmail(): boolean {
    return this.email.includes("@");
  }
}

// 2. Application Core (Use Cases & Ports)
export interface UserRepository {
  findById(id: string): Promise<User | null>;
  save(user: User): Promise<void>;
}

export class ChangeUserEmailUseCase {
  constructor(private readonly userRepo: UserRepository) {}

  async execute(userId: string, newEmail: string): Promise<void> {
    const user = await this.userRepo.findById(userId);
    if (!user) throw new Error("Utente non trovato");
    user.email = newEmail;
    if (!user.hasValidEmail()) throw new Error("Email non valida");
    await this.userRepo.save(user);
  }
}

// 3. Infrastructure / Adapter (External Implementation)
export class SqlUserRepository implements UserRepository {
  async findById(id: string): Promise<User | null> {
    return null;
  }
  async save(user: User): Promise<void> {}
}

Da ripassare

[ ] Regola di dipendenza e direzione delle frecce architetturali

[ ] Dependency Inversion vs Dependency Injection

[ ] Flusso dei dati con Presenter e Controller (Ports & Adapters)

[ ] Mappatura tra DTO, Domain Entities e ORM Entities

Riferimenti

Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin)

Jarvis Interviews
Jarvis