Guida Definitiva ai Pattern Architetturali: Cheat Sheet, Ports & Adapters e il binomio Clean/Onion

Nel mondo dello sviluppo software, la scelta dell’architettura è la decisione più critica che un team possa prendere. Un’architettura solida agisce come la spina dorsale di un’applicazione, determinando quanto sarà facile scalarla, testarla e mantenerla nel tempo. Al contrario, una scelta errata può trasformare un progetto di successo in un incubo di codice legacy inestricabile.

In questo articolo esploreremo i principali pattern architetturali attraverso un pratico cheat sheet di pro e contro. Successivamente, analizzeremo nel dettaglio l’Architettura Esagonale (Ports & Adapters) con un esempio pratico di scaffolding in Java. Infine, faremo chiarezza su due dei concetti più discussi (e spesso confusi) del settore: la Clean Architecture e la Onion Architecture.


Cheat Sheet dei Pattern Architetturali

Ogni pattern ha un suo scopo preciso. Non esiste una soluzione universale: esiste lo strumento giusto per il problema giusto. Ecco una panoramica rapida dei modelli più diffusi, dai tradizionali monoliti ai moderni sistemi distribuiti.

1. Monolite Tradizionale (Monolithic Architecture)

L’intera applicazione (UI, logica di business, accesso ai dati) è sviluppata, compilata e distribuita como un unico blocco di codice indivisibile.

  • Pro:
    • Semplice da sviluppare, testare e distribuire inizialmente (un solo pacchetto).
    • Nessuna latenza di rete interna: i componenti comunicano direttamente in memoria.
    • Gestione delle transazioni atomiche (ACID) semplice e nativa sul database unico.
  • Contro:
    • Scarsa scalabilità: per scalare una sola funzione pesante, bisogna duplicare l’intero monolite.
    • Rischio di “Big Ball of Mud”: con il tempo, i confini tra i moduli sfumano e il codice diventa un ammasso intrecciato.
    • Tempi di build e deploy molto lunghi man mano che il progetto cresce.

2. Monolite Modulare (Modular Monolith)

Il perfetto compromesso tra monolite e microservizi. L’applicazione viene distribuita come un unico blocco, ma il codice interno è rigidamente diviso in moduli indipendenti e isolati dal punto di vista logico, che comunicano tramite interfacce ben definite.

  • Pro:
    • Alta manutenibilità e confini di dominio chiari senza la complessità di rete dei microservizi.
    • Facilità di refactoring: se un domani un modulo deve diventare un microservizio separato, l’estrazione è immediata.
    • Sviluppo parallelo agevolato per team diversi che lavorano su moduli distinti.
  • Contro:
    • Richiede estrema disciplina da parte del team per evitare che i moduli violino i confini reciproci.
    • Rimane vincolato a un unico database (anche se diviso in schemi) e a un unico ciclo di rilascio.

3. Architettura a Livelli (Layered / N-Tier Architecture)

Il pattern tradizionale per eccellenza. Il codice è diviso in strati orizzontali (tipicamente: Presentazione, Logica di Business, Accesso ai Dati). Ogni livello ha un ruolo specifico e comunica solo con il livello inferiore.

  • Pro:
    • Facile da comprendere e implementare per team di ogni livello.
    • Separazione netta delle responsabilità (i componenti di UI non sanno come funziona il database).
    • Ottimo per applicazioni monolitiche di piccole e medie dimensioni.
  • Contro:
    • Forte accoppiamento: la logica di business dipende strettamente dal database o dai framework specifici.
    • Difficile da testare in isolamento senza fare ampio uso di mocking del database.
    • Tende a diventare disordinato e pesante man mano che l’applicazione cresce.

4. Architettura Orientata ai Servizi (Service-Oriented Architecture – SOA)

Un pattern enterprise in cui le applicazioni espongono le proprie funzionalità como servizi riutilizzabili. Spesso utilizza un componente centrale chiamato Enterprise Service Bus (ESB) per orchestrare la comunicazione, la trasformazione dei dati e l’integrazione tra sistemi diversi (spesso legacy).

  • Pro:
    • Massimo riutilizzo di sistemi enterprise preesistenti senza doverli riscrivere.
    • Accoppiamento allentato tra sistemi eterogenei grazie a standard di comunicazione formali (es. SOAP/WSDL o REST).
  • Contro:
    • L’ESB tende a diventare un single point of failure e un collo di bottiglia operativo.
    • Complessità tecnologica elevata e standard spesso troppo rigidi o pesanti.

5. Architettura a Microservizi (Microservices Architecture)

Divide l’applicazione in una collezione di servizi autonomi, focalizzati su un singolo dominio di business, che comunicano tra loro tramite protocolli leggeri (come API HTTP o message broker).

  • Pro:
    • Scalabilità indipendente: puoi scalare solo il servizio che riceve più traffico.
    • Flessibilità tecnologica: ogni microservizio può essere scritto con un linguaggio o database diverso.
    • Isolamento dei guasti: se un servizio si blocca, gli altri continuano a funzionare.
  • Contro:
    • Complessità operativa elevata (richiede automazione, monitoraggio avanzato e cultura DevOps).
    • Gestione della consistenza dei dati complessa (spesso richiede transazioni distribuite o consistenza eventuale).
    • Latenza di rete introdotta dalle continue chiamate tra servizi.

6. Architettura Orientata agli Eventi (Event-Driven Architecture)

Un pattern basato sulla produzione, rilevamento e consumo di eventi. I componenti comunicano in modo asincrono attraverso canali di messaggistica (pub/sub), reagendo ai cambiamenti di stato del sistema.

  • Pro:
    • Accoppiamento estremamente lasso (i produttori non sanno chi consuma i loro eventi).
    • Alta reattività e capacità di gestire carichi di lavoro intensivi in tempo reale.
    • Facilità di estensione: aggiungere una nuova funzionalità significa semplicemente agganciare un nuovo consumer.
  • Contro:
    • Flusso di esecuzione difficile da tracciare e fare debugging.
    • Rischio di perdita o duplicazione dei messaggi se non configurato correttamente.
    • Curva di apprendimento ripida per la gestione dello stato asincrono.

Focus: L’Architettura Esagonale (Ports & Adapters)

Nel 2005, Alistair Cockburn ha formalizzato l’Architettura Esagonale (nota anche come Ports and Adapters). L’obiettivo era rivoluzionario per l’epoca: proteggere il cuore dell’applicazione dalle tecnologie e dai framework esterni.

Il concetto è semplice: al centro c’è l’Applicazione (il dominio). Per interagire con l’esterno, l’applicazione mette a disposizione delle Porte (interfacce). Gli strumenti esterni (database, controller REST, code di messaggi) si collegano a queste porte tramite degli Adattatori (implementazioni concrete).

Esistono due tipi di porte:

  1. Inbound (o Driving) Ports: Interfacce che l’esterno chiama per far eseguire un’action all’applicazione (es. un caso d’uso attivato da una richiesta HTTP).
  2. Outbound (o Driven) Ports: Interfacce che l’applicazione chiama quando ha bisogno dell’esterno (es. salvare un dato nel database o inviare una notifica).

Scaffolding in Java con approccio Ports & Adapters

Ecco come strutturare i pacchetti in un progetto Java (es. Maven/Gradle con Spring Boot) seguendo rigidamente questo pattern per un ipotetico modulo di gestione ordini (order).

text

com.example.myapp.order
│
├── domain/                          [IL CUORE DELL'ESAGONO]
│   ├── model/
│   │   ├── Order.java               // Entità di dominio (puro Java, niente annotazioni JPA)
│   │   └── OrderId.java             // Value Object
│   │
│   └── exception/
│       └── OrderNotFoundException.java
│
├── ports/                           [LE INTERFACCE DELL'ESAGONO]
│   ├── inbound/                     // Cosa l'esterno può chiedere all'applicazione
│   │   └── CreateOrderUseCase.java  // Interfaccia del servizio applicativo
│   │
│   └── outbound/                    // Di cosa l'applicazione ha bisogno dall'esterno
│       └── OrderRepositoryPort.java // Interfaccia per il database
│
├── service/                         [L'APPLICAZIONE]
│   └── OrderService.java            // Implementa CreateOrderUseCase e usa OrderRepositoryPort
│
└── adapters/                        [L'ESTERNO CHE SI COLLEGA ALLE PORTE]
    ├── inbound/                     // Adattatori che guidano l'applicazione (Driving)
    │   └── web/
    │       ├── OrderRestController.java
    │       └── dto/
    │           └── CreateOrderRequest.java
    │
    └── outbound/                    // Adattatori guidati dall'applicazione (Driven)
        └── persistence/
            ├── OrderJpaRepository.java  // Interfaccia Spring Data JPA
            ├── OrderEntity.java         // Entità con annotazioni @Entity di Jakarta/Hibernate
            └── OrderPersistenceAdapter.java // Implementa OrderRepositoryPort e mappa Entity <-> Domain

Grazie a questa struttura, se decidi di passare da un database MySQL (JPA) a MongoDB, il codice nei pacchetti domain, ports e servicenon cambierà di una singola riga. Creerai semplicemente un nuovo adattatore in adapters.outbound.persistence.mongo che implementa la OrderRepositoryPort esistente.


Il Nodo della Disambiguazione: Clean Architecture vs Onion Architecture

Quando si parla di architetture incentrate sul dominio, due termini dominano la conversazione: la Onion Architecture (introdotta da Jeffrey Palermo nel 2008) e la Clean Architecture (formalizzata da Robert C. Martin, “Uncle Bob”, nel 2012).

Molti sviluppatori usano questi termini como sinonimi intercambiabili. C’è del vero in questo, ma è fondamentale fare chiarezza per evitare confusione concettuale.

Il principio fondamentale assoluto: La Direzione Unirezionale della Dipendenza

Sia l’architettura a cipolla che quella “pulita” si basano su una struttura a cerchi concentrici regolata da una legge ferrea e unidirezionale: un livello esterno può dipendere solo e unicamente dai livelli più interni, e mai il contrario.

Questo significa che:

  • Il cerchio più interno (il dominio/le entità) non sa assolutamente nulla dei cerchi che lo circondano. Non ha dipendenze, non importa classi esterne e non conosce i framework, il database o il web.
  • Il livello dei Casi d’Uso (subito sopra il dominio) conosce e usa le Entità, ma non sa come verranno mostrate a schermo o dove verranno salvate.
  • I livelli più esterni (Database, UI, Driver) conoscono e dipendono dai livelli interni per far funzionare l’applicazione.

Il flusso delle dipendenze punta sempre verso il centro. Questa totale asimmetria garantisce che il cuore del software rimanga puro e protetto dagli shock tecnologici esterni.

Onion Architecture: Focus sul Modello di Dominio

La Onion Architecture posiziona il Modello di Dominio al centro esatto della “cipolla”. Intorno ad esso si sviluppano i servizi di dominio e i servizi applicativi. Il database e la UI si trovano nel cerchio più esterno.

  • La sua peculiarità: Utilizza pesantemente le interfacce (definite nei cerchi interni) per invertire la dipendenza verso le implementazioni concrete (che risiedono nel cerchio esterno).

Clean Architecture: Una generalizzazione standardizzata

La Clean Architecture riprende i concetti della Onion Architecture (e della precedente architettura Esagonale) e li standardizza utilizzando una terminologia specifica e rigida: Entities (il cuore del business), Use Cases (la logica specifica dell’applicazione), Interface Adapters (i controller e i presenter) e Frameworks & Drivers (il web, i database).

  • La sua peculiarità: Introduce regole molto dettagliate su come i dati devono attraversare i confini dei cerchi (tramite oggetti di puro trasferimento dati o DTO) e su come i Controller debbano interagire con i Presenter attraverso pattern come l’Inversion of Control.

In sintesi: Come distinguerle?

Non si tratta di due architetture in competizione, ma di due sfumature della stessa filosofia progettuale.

La Onion Architecture è il concetto teorico focalizzato sull’inversione delle dipendenze attorno al dominio; la Clean Architecture è l’evoluzione pratica e raffinata che fornisce una mappa dettagliata e una terminologia universale per implementare quella stessa separazione basata sulle dipendenze centripete. Se applichi correttamente i principi dell’Architettura Esagonale o della Onion Architecture, con ottima probabilità starai scrivendo un’applicazione che rispetta anche i canoni della Clean Architecture.

Conclusione

Non esiste l’architettura perfetta in assoluto, esiste l’architettura adatta ai requisiti del tuo progetto. Per un piccolo prototipo o un software CRUD minimale, un’architettura a livelli o un monolite tradizionale saranno più che sufficienti. Per un sistema enterprise destinato a durare anni e a cambiare spesso, investire in un approccio a moduli isolati (come Ports & Adapters o Clean Architecture) eviterà al tuo codice di diventare obsoleto, mantenendolo testabile, flessibile e pronto al cambiamento.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *