Pattern architetturali del software 🧱
La buona architettura non fa miracoli, ma evita i disastri. Il che, in produzione, vale quanto un miracolo ben riuscito.
In questo documento trovi un elenco ragionato dei principali pattern architetturali e di design, suddivisi in macro categorie. L’obiettivo è darti un lessico condiviso e una mappa pratica per scegliere cosa usare, quando e perché — senza innamorarsi della buzzword del momento.
Nota di stile: nomi “storici” come master/slave li troverai aggiornati a terminologia più corretta (es. primario/replica). I diagrammi li lasciamo per la lavagna; qui andiamo dritti al punto.
Architetture applicative (strutturali) 🧩
Pattern che definiscono la struttura interna di un’applicazione e la separazione delle responsabilità.
Layered (n‑tier) 🧩
Organizza l’app in livelli (presentazione, applicativo, dominio, persistenza) con dipendenze unidirezionali verso il basso.
- Pro: struttura chiara, testabilità elevata, onboarding facile, riuso per layer.
- Contro: rigidità tra layer, passaggi “a cascata” che aggiungono latenza; tentazione di “bucare i layer”.
- Esempio: app enterprise CRUD con validazioni e regole moderate.
MVC (model‑view‑controller) 🧩
Separa la gestione dello stato (Model), la presentazione (View) e il flusso/interazioni (Controller) — classico nelle web UI.
- Pro: separazione delle responsabilità, testabilità delle viste e dei controller, parallelismo tra ruoli.
- Contro: può risultare verboso; in domini semplici rischia di aggiungere strati inutili.
- Esempio: e‑commerce con viste catalogo/carrello e controller per azioni utente.
Hexagonal (ports & adapters) 🧩
Metti il dominio al centro; tutto ciò che è I/O (DB, API, code, UI) si collega via “porte” (interfacce) e “adattatori”.
- Pro: indipendenza tecnologica, testabilità eccellente, sostituibilità degli adapter.
- Contro: più astrazioni da gestire, curva iniziale.
- Esempio: core pagamenti con adapter multipli per PSP diversi.
Microkernel (plug‑in) 🧩
Un nucleo minimale fornisce servizi base estendibili tramite plugin indipendenti.
- Pro: estendibilità, sperimentazione rapida, isolabilità dei plugin.
- Contro: gestione del ciclo di vita plugin, compatibilità API del core.
- Esempio: motore di regole o scheduler con job plugin.
Pipes & filters 🧩
Catena di filtri indipendenti che trasformano dati lungo una pipeline.
- Pro: composizione, riuso dei filtri, parallelizzazione.
- Contro: overhead di serializzazione tra filtri, colli di bottiglia.
- Esempio: pipeline log → parse → enrich → route → store.
Monolite modulare 🧩
Un’unica codebase/processo organizzato in moduli ben isolati con confini espliciti e dipendenze controllate.
- Pro: semplicità operativa, transazioni locali, deploy unificato.
- Contro: rischi di accoppiamento se i confini non sono rispettati; scala solo fino a un certo punto.
- Esempio: applicazione business con moduli per ordini, fatture, magazzino e report.
Onion / Clean architecture 🧩
Varianti che, come l’esagonale, mantengono il dominio al centro stratificando dipendenze verso l’esterno.
- Pro: testabilità, indipendenza dai framework, facilità di sostituzione dell’infrastruttura.
- Contro: più astrazioni, curva di adozione.
- Esempio: servizio core con use case nel layer application e interfacce di repository al bordo.
MVVM (model‑view‑viewmodel) 🧩
Pattern UI che separa la View dalla logica di presentazione (ViewModel). Il ViewModel media e adatta il Model per la View, esponendo stato e comportamenti; ideale per il data‑binding.
- Pro: testabilità della logica di presentazione; riuso e binding.
- Contro: complessità se il binding non è gestito con disciplina.
- Esempio: app mobile/desktop con binding reattivo tra View e ViewModel.
Architetture distribuite e di integrazione 🌐
Pattern che organizzano sistemi composti da più componenti/servizi e il loro dialogo.
Microservizi 🌐
App composta da servizi piccoli, autonomi e indipendentemente distribuibili, idealmente ciascuno proprietario dei propri dati.
- Pro: scalabilità mirata, velocità di rilascio per team, resilienza ai guasti locali.
- Contro: transazioni distribuite, osservabilità, costi operativi più alti.
- Esempio: e‑commerce con servizi separati per catalogo, ordini, pagamenti, raccomandazioni.
SOA (service‑oriented) 🌐
Servizi più “coarse‑grained” e standardizzati; spesso orchestrati, storicamente con ESB, oggi più leggeri.
- Pro: contratti stabili, composizione di servizi, governance.
- Contro: rischio “bus” centrale come collo di bottiglia; rigidità.
- Esempio: servizi aziendali di anagrafica, fatturazione, reporting condivisi da più applicazioni.
Event‑driven 🌐
Comunicazione asincrona basata su eventi prodotti e consumati da componenti disaccoppiati.
- Pro: decoupling, scalabilità, estendibilità (nuovi consumer senza toccare i producer).
- Contro: ordering e deduplica, schema evolution, consistenza eventuale.
- Esempio: ordini→evento “OrderPlaced”→notifiche/spedizioni/analytics.
Broker 🌐
Un mediatore gestisce routing, trasformazioni e invocazioni remote tra componenti.
- Pro: decoupling, osservabilità centralizzata.
- Contro: latenza, punto di contenzione, lock‑in.
- Esempio: integrare sistemi legacy via message broker con trasformazioni di schema.
Pub/Sub 🌐
Produttori pubblicano su topic, consumatori si sottoscrivono e ricevono eventi.
- Pro: aggiunta di consumer senza impatti, scalabilità orizzontale.
- Contro: test end‑to‑end, gestione ordering/partizionamento.
- Esempio: invio notifiche push ed email a partire da un unico evento.
API gateway 🌐
Punto d’ingresso unico che fornisce routing, auth, rate limiting, aggregazione e osservabilità verso servizi a valle.
- Pro: semplifica i client, centralizza policy e sicurezza.
- Contro: rischio di collo di bottiglia; configurazioni complesse.
- Esempio: gateway che espone endpoint pubblici e smista verso microservizi interni.
Backend for Frontend (BFF) 🌐
Un backend dedicato per ciascun tipo di front‑end (web, mobile) per adattare API, performance e aggregazioni.
- Pro: API su misura per esperienza utente; riduce over/under‑fetching.
- Contro: più artefatti da mantenere; rischio duplicazioni.
- Esempio: BFF mobile con risposte aggregate e payload minimizzati.
Service mesh / sidecar 🌐
Strato infrastrutturale (es. Envoy/istio) che sposta in sidecar networking, mTLS, retry, circuit breaker, osservabilità.
- Pro: policy consistenti senza toccare il codice; telemetria ricca.
- Contro: complessità operativa, consumo risorse.
- Esempio: microservizi con proxy sidecar per mTLS e controllo traffico.
Service discovery 🌐
Risoluzione dinamica degli endpoint dei servizi (DNS/registry) per permettere a client di trovare istanze attive.
- Pro: resilienza a cambi/scale; niente endpoint hardcoded.
- Contro: complessità di health check e caching.
- Esempio: client che risolvono istanze via consul/eureka/DNS.
Serverless (FaaS/Event‑driven) 🌐
Funzioni gestite dal provider, attivate da eventi/HTTP, con scalabilità automatica e billing a consumo.
- Pro: time‑to‑market rapido, scalabilità automatica, costi variabili.
- Contro: cold start, limiti runtime, osservabilità e local dev più difficili.
- Esempio: funzione che processa eventi da coda e aggiorna un datastore.
Client‑server 🌐
Client che richiede risorse/servizi, server che elabora e risponde.
- Pro: centralizzazione dei dati, sicurezza semplificata.
- Contro: single point of failure se non ridondato.
- Esempio: app mobile che interagisce con API REST.
P2P (peer‑to‑peer) 🌐
Nodi paritari senza centro unico, ognuno può essere client e server.
- Pro: assenza di SPOF, scalabilità con i nodi.
- Contro: sicurezza e qualità del servizio variabili.
- Esempio: file sharing o edge sync.
Controller‑responder 🌐
Un controller coordina operazioni di write; nodi responder servono letture/repliche.
- Pro: isolamento del carico di lettura, riduzione contesa.
- Contro: sincronizzazione e lag tra copie.
- Esempio: API che legge da replica e scrive sul primario.
Dati, query e persistenza 💾
Pattern focalizzati su come modellare accesso e gestione dei dati.
CQRS 💾
Separazione netta tra il modello di scrittura (command) e quello di lettura (query) per ottimizzarli indipendentemente. Non implica necessariamente database separati: la separazione può essere solo logica.
- Pro: performance in lettura, modelli dedicati, scala indipendente.
- Contro: coerenza eventuale, maggiore complessità.
- Esempio: read model denormalizzato per dashboard analitiche.
Event sourcing 💾
Lo stato deriva dalla sequenza di eventi append‑only; si ricostruisce “riproducendoli”.
- Pro: tracciabilità totale, time‑travel, possibilità di nuove proiezioni.
- Contro: migrazione eventi, evoluzione schemi, storage crescente.
- Esempio: contabilità, ordini con timeline completa.
Repository 💾
Astrazione dell’accesso ai dati dietro interfacce del dominio.
- Pro: testabilità, separazione di responsabilità.
- Contro: rischio di coprire feature specifiche del datastore utili.
- Esempio:
OrderRepository.findOpenByCustomerId(...).
Sharding 💾
Partizionamento dei dati su più nodi in base a una chiave (es. customerId).
- Pro: scalabilità orizzontale, isolamento del carico.
- Contro: hotspot se la chiave è sbilanciata, complessità operativa.
- Esempio: multi‑tenant con shard per cliente/regione.
Primario/replica (replicazione) 💾
Scritture su primario, letture su repliche asincrone/sincrone.
- Pro: offload del read path, failover.
- Contro: lag, inconsistenze temporanee.
- Esempio: reporting su replica, operazioni critiche sul primario.
Static content hosting 💾
Separazione e consegna di asset statici via CDN/edge rispetto al contenuto dinamico.
- Pro: latenza bassa, costi ridotti sul backend.
- Contro: invalidazione e coerenza cache.
- Esempio: immagini, CSS/JS su CDN; API dinamiche dal backend.
Cache‑aside 💾
Il codice applicativo legge prima dalla cache e, in caso di miss, carica dal database popolando la cache.
- Pro: semplice, riduce latenza e carico sul DB.
- Contro: invalidazione complessa, rischio di dati stantii.
- Esempio: lookup di profili utente con TTL e invalidazione su update.
Read‑through cache 💾
La cache intercetta le letture e, in caso di miss, carica automaticamente dal datastore popolandosi (trasparente al chiamante).
- Pro: semplicità lato applicativo; warm automatico; riduce la duplicazione di logica di loading.
- Contro: controllo minore sull’invalidazione; rischio di stampede senza protezioni; dipendenza dalle capacità del cache layer.
- Esempio: catalogo prodotti che la cache recupera automaticamente dal DB al primo accesso.
Outbox (transactional outbox) 💾
Scrivi eventi in una tabella “outbox” nella stessa transazione del write; un processo li pubblica in modo affidabile.
- Pro: garantisce pubblicazione affidabile di eventi evitando perdite/duplicati.
- Contro: processo di dispatch da gestire; ordering per aggregato.
- Esempio: al salvataggio di un ordine, persisti l’evento in outbox e pubblica su broker.
Lambda / Kappa architecture 💾
Architetture dati per analytics: Lambda combina batch+speed layer; Kappa usa solo stream per semplificare. Nota: sono architetture di sistema più che pattern di sola persistenza.
- Pro: scalano su grandi volumi, consentono elaborazioni real‑time.
- Contro: complessità infrastrutturale (Lambda), semantiche di stream (Kappa).
- Esempio: pipeline eventi con proiezioni real‑time e calcoli batch di consolidamento.
Affidabilità, resilienza e controllo del traffico 🛡️
Pattern che aiutano a non far cadere tutto quando qualcosa va storto (perché succederà).
Circuit breaker 🛡️
Interrompe chiamate verso dipendenze degradate/apparse fallimentari per evitare valanghe di errori.
- Pro: stabilizza il sistema, protegge le risorse.
- Contro: gestione stati (open/half‑open/closed), tuning non banale.
- Esempio: degrado di funzionalità “non essenziali” quando un servizio è giù.
Bulkhead (paratie stagne) 🛡️
Isola risorse (thread pool/connessioni) per componenti diversi, impedendo che un guasto saturi l’intero sistema.
- Pro: confinamento dei guasti e protezione delle risorse.
- Contro: tuning dei limiti e frammentazione delle risorse.
- Esempio: pool separati per chiamate a servizi A/B.
Queue‑based load leveling 🛡️
Metti in coda il lavoro per assorbire picchi, con consumer che processano al ritmo sostenibile.
- Pro: livella picchi, protegge i backend, aumenta resilienza.
- Contro: latenza aggiunta, gestione DLQ e ordering.
- Esempio: coda per processare ordini invece di chiamare direttamente il servizio lento.
Retry/Timeout con backoff 🛡️
Imposta timeouts e retry con backoff/jitter per gestire errori transitori senza accentuare la congestione.
- Pro: recupero automatico da glitch, stabilità.
- Contro: se mal configurati aggravano il problema (retry storm).
- Esempio: chiamate HTTP con timeout brevi e retry esponenziale limitato.
Saga 🛡️
Coordinazione di transazioni distribuite tramite passi locali e azioni compensative.
- Pro: consistenza logica, scalabilità.
- Contro: maggiore complessità, casi di errore numerosi.
- Esempio: ordine → pagamento → riserva stock → spedizione con rollback appropriati.
Throttling / rate limiting 🛡️
Limitazione del traffico per proteggere risorse, SLA e costi.
- Pro: prevedibilità dei carichi, isolamento.
- Contro: esperienza utente impattata se aggressivo.
- Esempio: 100 req/min per API free tier.
Scalabilità e performance ⚡
Pattern pensati per reggere carico e variazioni di traffico.
Space‑based ⚡
Distribuisce stato e carico tra unità di elaborazione stateless, con middleware che gestisce dati in memoria, messaging e scalabilità.
- Pro: assenza di colli centrali, scalabilità lineare.
- Contro: complessità di coerenza e test end‑to‑end.
- Esempio: sistemi di offerte/aste con picchi improvvisi.
Evoluzione e modernizzazione 🔧
Strangler 🔧
Si interpone fra client e legacy con una facciata; migra funzionalità una alla volta finché il legacy “si spegne”.
- Pro: rischio ridotto, rollback più semplici, misurabilità.
- Contro: doppio run durante la transizione, routing complesso.
- Esempio: migrare endpoint legacy verso servizi nuovi con proxy a routing configurabile (feature flag/gradual rollout).
Anti‑corruption layer (ACL) 🔧
Strato che traduce/modella tra sistemi o bounded context per proteggere il tuo modello dalle “impurità” esterne.
- Pro: isola il dominio da modelli terzi; riduce accoppiamento semantico.
- Contro: codice di traduzione da mantenere; latenza aggiunta.
- Esempio: adapter che mappa DTO legacy in Value Object del dominio.
Approcci strategici di modellazione 🎯
Domain‑Driven Design (DDD) 🎯
Approccio di modellazione che guida le scelte architetturali con linguaggio ubiquo, bounded context, aggregati e separazione del core domain.
- Pro: allineamento col business, confini chiari, codice espressivo.
- Contro: investimento iniziale, rischio over‑engineering su CRUD semplici.
- Esempio: vedi anche “Domain Driven Design 🧩” in questa sezione dei docs.
Come scegliere (in 5 mosse) 🧭
- Chiarisci qualità attese: performance, scalabilità, time‑to‑market, regolatorio. No, non puoi ottimizzare tutto al massimo contemporaneamente.
- Valuta il dominio: complesso e in evoluzione? DDD + confini chiari; data‑intensive? CQRS/event sourcing; integrazione pesante? SOA/event‑driven.
- Guarda l’organizzazione: team piccoli e pochi SRE? Monolite ben fatto o modulare. Team multipli autonomi? Microservizi, ma con piattaforma e osservabilità mature.
- Fai i conti con l’ecosistema: cloud‑native, legacy, vendor lock‑in, skill interni. L’architettura “perfetta” che il team non sa gestire… non è perfetta.
- Procedi in modo incrementale: feature flag, strangler, misure oggettive. Cambiare rotta presto costa poco; tardi, costa.
Mappa rapida d’uso 👇
- Vuoi partire veloce su un dominio non complicato? Layered/MVC + repository.
- Tante letture e query difficili? CQRS (+ proiezioni ad‑hoc).
- Reattività tra servizi eterogenei? Event‑driven + pub/sub.
- Domini ad alto valore e regole toste? DDD + hexagonal.
- Modernizzi un legacy mission‑critical? Strangler + test di caratterizzazione.
- Picchi imprevedibili? Space‑based + caching/edge.
Confronto sintetico tra pattern 🧪
Di seguito una tabella compatta per alcuni pattern chiave. Le valutazioni sono indicative (basso/medio/alto) e vanno sempre contestualizzate.
| Pattern | Scalabilità | Complessità | Team fit |
|---|---|---|---|
| Layered | Medio | Bassa | Team piccoli/eterogenei |
| MVC | Medio | Media | Team con competenze UI/BE separate |
| Hexagonal | Medio | Media | Team orientati al dominio/test |
| Microkernel | Medio | Media | Team che gestiscono plugin/estensioni |
| Pipes & Filters | Alto | Media | Team data/processing |
| Microservizi | Alto | Alta | Team multipli autonomi + SRE/Platform |
| SOA | Medio | Media | Enterprise con governance forte |
| Event‑driven | Alto | Alta | Team con skill messaggistica/stream |
| Pub/Sub | Alto | Media | Team integrazione/real‑time |
| Client‑server | Medio | Bassa | Team generalisti |
| P2P | Alto | Alta | Team con esperienza reti/distribuito |
| API gateway | N/D | Media | Team BE/Platform |
| BFF | Medio | Media | Team FE/BE collaborativi |
| Service mesh/sidecar | N/D | Alta | Team SRE/Platform |
| Service discovery | N/D | Media | Team BE/DevOps |
| Serverless (FaaS) | Alto | Media | Team che adottano servizi gestiti |
| CQRS | Alto (read) | Alta | Team con competenze su proiezioni |
| Event sourcing | Medio | Alta | Team data‑intensive/audit |
| Repository | N/D | Bassa | Qualsiasi team orientato a test |
| Sharding | Alto | Alta | Team DBA/operativo esperto |
| Primario/replica | Medio (read) | Media | Team BE con DBA |
| Static hosting | Alto | Bassa | Team web/app con CDN |
| Cache‑aside | N/D | Media | Team BE/API |
| Read‑through cache | N/D | Media | Team BE/API |
| Outbox | N/D | Media | Team BE/DB/stream |
| Lambda/Kappa | Alto | Alta | Team data/stream |
| Circuit breaker | N/D | Media | Team BE/Platform |
| Bulkhead | N/D | Media | Team BE/Platform |
| Queue‑based leveling | N/D | Media | Team BE/Platform |
| Retry/Timeout | N/D | Bassa | Team BE |
| Saga | N/D | Alta | Team distribuiti esperti |
| Throttling | N/D | Media | Team API/gateway |
| Space‑based | Alto | Alta | Team performance/low‑latency |
| Strangler | N/D | Media | Team modernizzazione |
| Anti‑corruption layer | N/D | Media | Team DDD/integration |
| Monolite modulare | Medio | Media | Team piccoli/medi |
| Onion/Clean | Medio | Media | Team domain‑centric/test |
| MVVM | Medio | Media | Team UI |
| DDD | N/D | Media‑Alta | Team prodotto/domain‑centric |
Conclusione ✅
I pattern sono strumenti, non religioni. Scegli quelli che massimizzano il valore nel tuo contesto, sorveglia gli effetti nel tempo e mantieni il sistema evolvibile. La vera seniority sta nel sapere cosa NON applicare.