L'architettura pulita è più che una parola chiave nello sviluppo moderno del software: è un approccio deliberato e strutturato per progettare sistemi che si basano sulla prova del tempo. Al suo centro, l'architettura pulita fornisce una serie di linee guida per l'organizzazione del codice in modo che la logica aziendale rimanga indipendente da influenze esterne come quadri, database, interfacce utente e servizi di terze parti.

Cos'è l'architettura pulita?

L'architettura pulita è stata divulgata da Robert C. Martin (spesso chiamato zio Bob) nel suo libro Clean Architecture: A Craftsman's Guide to Software Structure and Design[[] e in una serie di post sul blog. La filosofia principale è di separare le preoccupazioni definendo strati concentrici di responsabilità, con lo strato più interno contenente la logica più pura degli affari e le agenzie esterne di gestione dei database come web.

Questo approccio non è radicalmente nuovo, si trae pesantemente da precedenti modelli come l'architettura esagonale (Alistair Cockburn), l'architettura della cipolla (Jeffrey Palermo), e il design a domini (Eric Evans). Ciò che l'architettura pulita porta alla tabella è un set chiaro e ripetibile di regole che qualsiasi squadra può adottare, indipendentemente dal linguaggio o dal quadro.

Attraverso questa regola, gli sviluppatori assicurano che i cambiamenti nei quadri, nei database o nelle tecnologie dell'interfaccia utente non si introducano a corrompere la logica del core business.

Principi chiave di architettura pulita

I principi sono volti a guidare il processo decisionale in tutto il ciclo di vita di un progetto, esaminiamo in profondità ogni principio.

1. Indipendenza dei Quadri

In architettura pulita, la logica aziendale non dovrebbe essere indurita in un quadro specifico. Ad esempio, se si sta costruendo un'applicazione web con un framework come React, Angular, o Vue, la logica del dominio centrale non dovrebbe importare componenti React o servizi di riferimento Angular. Invece, il framework dovrebbe essere trattato come una preoccupazione di strato esterno che si collega a interfacce ben definite.

2. Testabilità

Quando le regole aziendali sono isolate dalle dipendenze esterne, diventano trivialmente provabili. È possibile scrivere test di unità per le entità e casi di utilizzo senza filare un database, mocking un server HTTP, o caricare un'interfaccia utente. Questa velocità e affidabilità di test incoraggia gli sviluppatori a testare più spesso e prima, catturando bug prima che escalano. Inoltre, perché gli strati esterni (come database e API) sono anche progettati intorno alle interfacce, è possibile scrivere facilmente il codice completo.

3. Separazione delle preoccupazioni

L'architettura pulita divide un sistema in strati, ciascuno con una responsabilità separata. Il livello più interno (Entities]) contiene regole aziendali a livello aziendale. Il livello successivo verso l'esterno ( Utilizzare i casi)) gestisce i flussi di lavoro specifici per l'applicazione.

4. La regola di dipendenza

Questa regola è la colla che tiene insieme l'architettura. Si afferma che le dipendenze del codice sorgente devono sempre puntare verso l'interno: da strati esterni verso strati interni. Nessun strato interno dovrebbe mai sapere su uno strato esterno. In pratica, questo significa che le interfacce definite nei casi di utilizzo e le entità sono di proprietà di quegli strati interni.

Layers of Clean Architecture

Mentre il numero di strati può variare a seconda del progetto, il diagramma di architettura pulita canonica mostra quattro anelli concentrici. Capire ogni strato è essenziale per applicare correttamente i principi.

Entità (Regole aziendali d'impresa d'impresa)

Le entità sono la parte più stabile del sistema, incapsulano le regole fondamentali del business che sono applicabili in tutta l'organizzazione. Ad esempio, in un sistema bancario, un'entità [] conterrebbe metodi come e ] che applicano invarianti come "l'equilibrio non deve mai andare al di sotto dello zero".

Utilizzare i casi (regolamenti di applicazione)

I casi di utilizzo definiscono come il sistema si comporta dalla prospettiva di un attore (utente umano, un altro sistema o un timer), orchestrano il flusso di dati da e verso le entità e indirizzano le entità a eseguire le loro regole aziendali. Ad esempio, un chiamerebbe metodi sulle entità e quindi persistono il risultato attraverso un'interfaccia di repository.

Adattatori di interfaccia

Questo livello converte i dati tra il formato più conveniente per i casi di utilizzo e le entità (propicamente le strutture di dati semplici) e il formato richiesto dalle agenzie esterne.

  • Controllers[]] che parse HTTP richiede e chiamare il caso di utilizzo appropriato.
  • Presenters[[]] che trasformano l'uscita della cassa di uso in un formato adatto per l'interfaccia utente, come un modello di vista.
  • Database gateways[]] che implementano interfacce di repository e traducono tra dati di entità e operazioni SQL o NoSQL.
  • Adattatori client API[[]] che chiamano servizi esterni e convertono le risposte nelle strutture di dati dello strato interno.

Lo strato dell'adattatore è dove vive la maggior parte del codice "glue", ma è anche lo strato che tende a cambiare più frequentemente, mentre le tecnologie esterne si evolvono.

Quadri e Driver

L'anello più esterno contiene tutte le tecnologie concrete che il sistema utilizza: il server web (ad esempio, Express, Django, Spring Boot), il sistema di gestione del database (ad esempio, PostgreSQL, MongoDB), il framework UI (ad esempio, React, Angular), e così via. Questo strato dovrebbe essere il più sottile possibile.

Vantaggi dell'utilizzo di Architettura pulita

L'adozione di architettura pulita produce vantaggi concreti e a lungo termine che superano lo sforzo di strutturazione del codice in questo modo.

  • Maintainability:[] Quando è necessario modificare una funzione, si modifica solo il caso di utilizzo rilevante e le sue entità, non l'intera applicazione. Poiché le dipendenze sono dirette verso l'interno, i cambiamenti negli strati esterni (come la sostituzione di un database) raramente si inseriscono nella logica aziendale.
  • Testability:[] Come accennato in precedenza, il basso accoppiamento significa che è possibile testare le regole aziendali in isolamento senza impostare un ambiente completo. Questo porta a più veloci loop di feedback e una maggiore fiducia nel codice.
  • Flessibilità:[] Puoi rimandare le decisioni sull'infrastruttura. Ad esempio, puoi iniziare con una semplice persistenza basata su file e passare successivamente a un database relazionale senza riscrivere la logica aziendale, finché l'interfaccia del repository rimane la stessa.
  • Scalability:[] L'architettura pulita non fa automaticamente scalare il sistema in orizzontale, ma supporta la scalabilità del team. Separando le preoccupazioni in strati, i membri del team diversi (o anche i team diversi) possono lavorare sull'interfaccia utente, database e logica aziendale simultaneamente senza passare sui piedi dell'altro.
  • Imbarco e collaborazione:[] I nuovi sviluppatori possono comprendere rapidamente la struttura complessiva perché l'architettura segue un modello ben noto, che può immergersi in strati specifici senza dover comprendere l'intera base di codice.

Per un tuffo più profondo nella motivazione dietro questo modello, è possibile leggere l’originale di Robert C. Martin ]Clean Architecture blog post o esplorare Martin Fowler’s discussione sull’osservabilità orientata al dominio, che completa la filosofia di architettura pulita.

Implementare Architettura pulita nella pratica

Trasferirsi all'architettura pulita può sentirsi intimidatorio, soprattutto se si sta lavorando con un codice base legacy.

Passo 1: Identificare e isolare il Dominio del nucleo

Iniziate esaminando il vostro codice esistente per individuare le regole aziendali pure. Queste sono le parti che avrebbero ancora senso se avete sostituito il database o l'interfaccia utente domani. Estrarli in un modulo separato (ad esempio, un pacchetto, una cartella, o un microservizio) senza dipendenze esterne. Questo modulo diventerà le vostre entità e casi di utilizzo.

Fase 2: Definire le interfacce per le interazioni esterne

Per ogni operazione che richiede un sistema esterno (database, file system, rete, UI), definire un'interfaccia dalla prospettiva del core. Ad esempio, creare un interfaccia con metodi come e . Non preoccupatevi ancora dell'implementazione; l'interfaccia appartiene al core.

Passo 3: Creare adattatori che implementano quelle interfacce

Per un adattatore di database, questa potrebbe essere una classe di repository che utilizza il tuo ORM o SQL raw. Per un adattatore UI, questo potrebbe essere un controller e un presentatore che trasforma i dati per una vista web. La chiave è assicurarsi che il core non importa mai questi adattatori direttamente.

Passo 4: Filo tutto insieme nel livello quadro

Utilizzare l’iniezione di dipendenza, sia attraverso un contenitore, una radice di composizione o un cablaggio manuale, per collegare gli adattatori al core all’avvio. Questa è la responsabilità dello strato più esterno. Ad esempio, in una tipica applicazione web, il punto di ingresso principale crea l’adattatore di database, il caso di utilizzo e il controller, quindi avvia il server.

Passo 5: Applicare la rifattoria continua

Come si aggiungono le funzionalità, controllare continuamente che il nuovo codice non viola la regola di dipendenza. Utilizzare strumenti per far rispettare i confini architettonici (ad esempio, ArchUnit per Java, o PHPStan con regole personalizzate per PHP).

Pitfalls comuni da evitare

  • I piccoli progetti di ingegneria superiore:[] L'architettura pulita aggiunge indiretta. Per una semplice applicazione CRUD con un singolo caso di utilizzo e senza cambiamenti attesi, la testa non può valere.
  • Il codice di framework di lettura nel core:[ È sorprendentemente facile importare un'utilità di framework fuori dalla convenienza. Ad esempio, utilizzando un'annunciazione di ORM in una classe di entità.
  • Creare troppe interfacce prematuramente:[ Non avete bisogno di un'interfaccia per ogni singola classe. Solo astratto quello che vi aspettate di variare. Iniziate con i principali confini esterni (database, UI, file system) e generalizzate più tardi se necessario.
  • Ignorando i confini di gestione degli errori:[ Come le eccezioni vengono gettate e catturate attraverso i confini dello strato richiedono un design attento. Gli strati interni dovrebbero lanciare eccezioni di business che sono significative al core.

Esempio reale: un semplice sistema di elaborazione dell'ordine

Considera un'applicazione e-commerce che deve effettuare un ordine. In un approccio di architettura pulita:

  • Entities:[ , [], []] con regole di business come “un ordine deve avere almeno un elemento” e “la scorta di un prodotto non può andare negativa.”
  • ]Usa Caso:[] ] riceve una richiesta contenente l'ID del cliente e la lista dei prodotti. Chiama l'interfaccia per salvare l'ordine e il per aggiornare lo stock.
  • Aggiungitori di interfaccia:[] A ] estrae i dati dalla richiesta HTTP, chiama il caso di utilizzo, e poi un trasforma il risultato in una risposta JSON.
  • Frameworks and Drivers:[] La radice di composizione imposta un server web, inizializza il pool di connessione del database e collega tutte le dipendenze insieme. Il framework web (ad esempio, Express.js o Spring Boot) appare solo in questo anello esterno.

Se poi decidete di passare da PostgreSQL a MongoDB, dovete solo scrivere un nuovo [ e aggiornare la radice della composizione. La custodia e le entità di utilizzo rimangono intatte. Se volete aggiungere un nuovo canale di notifica come SMS, create un altro adattatore e registratelo—di nuovo senza alterare il nucleo.

Quando si dovrebbe adottare architettura pulita?

L’architettura pulita non è una pallottola d’argento, ma è molto preziosa nei progetti con una complessità moderata e alta, una durata di vita lunga e attesa, o un dominio aziendale che è centrale per il successo dell’azienda.

  • Si anticipano frequenti modifiche alle regole aziendali.
  • Il sistema deve integrarsi con più database o servizi esterni che possono cambiare.
  • Hai un team di sviluppatori che devono lavorare in parallelo.
  • Si stanno costruendo sistemi che servono più client UIs (web, mobile, desktop) dallo stesso backend.

D'altra parte, per piccoli prototipi, script one-off o progetti con un ciclo di vita molto breve, un'architettura più semplice (come una struttura MVC piana) può essere più pragmatica.

Conclusioni

L'architettura pulita è un approccio collaudato per creare codebases che rimangono mantenuti, testabili e adattabili nel corso degli anni di sviluppo. Con l'intento di rafforzare la regola di dipendenza e separare le preoccupazioni in strati distinti, gli sviluppatori possono isolare la logica del core business dall'inevitabile scio di tecnologie esterne. L'investimento in avanti nella progettazione di interfacce e nell'organizzazione del codice paga quando è necessario aggiungere funzionalità, passare database o crescere nuovi membri del team.

Per ulteriori informazioni sulla modellazione di dominio e l'architettura pulita in specifici linguaggi di programmazione, si può fare riferimento a [Domain-Driven Design Community[] o all'articolo ]explicit architecture di Herberto Graça]] che lega insieme diversi modelli.