Salta al contenuto principale

05 - Clean Architecture Spring Boot Template: il gate ArchUnit che gira davvero

Un template Spring Boot 3.4 a Clean Architecture con un gate ArchUnit che esegue davvero quattro regole, estratto a mano da un backend di produzione.

Cosa ne pensi di questo articolo?

05 - Clean Architecture Spring Boot Template: il gate ArchUnit che gira davvero
Original Researchfedericocalo.dev

In Breve

Un template Spring Boot 3.4 a Clean Architecture con un gate ArchUnit che esegue davvero quattro regole, estratto a mano da un backend di produzione.

Risposta diretta

Ho estratto un template Clean Architecture da un backend Spring Boot di produzione con 25 verticali proprietari, curando i file a mano invece di usare git filter-repo. Il risultato e un repo pubblico a 4 layer con JWT, Flyway e un gate ArchUnit che gira davvero quattro regole, verde su JDK 21.

La maggior parte dei template "Clean Architecture" su GitHub mente per omissione: mostra un bel diagramma a quattro layer nel README e un test ArchUnit nel src/test, ma quel test gira zero regole ed e verde per finta. Un diagramma di architettura non vale nulla finche un test non lo forza a ogni build. Questo template nasce da quella lezione, imparata in produzione nel modo peggiore.

Il repo clean-architecture-spring-boot-template e una estrazione sanitizzata del backend reale del mio portfolio: stesso stack (Spring Boot 3.4, Java 21, jjwt, Flyway, ArchUnit, Resilience4j), ma ripulito da ogni logica di business e da ogni segreto. Non e uno scheletro inventato per l'occasione: e il distillato generico di codice che gira davvero, con l'unica differenza che l'aggregato di esempio e un banale Task invece dei verticali proprietari.

Perche non ho usato git filter-repo

La tentazione, estraendo un template da un monorepo, e automatizzare: un git filter-repo --path domain --path application e via. Nel mio caso sarebbe stato un disastro. I quattro package domain, application, infrastructure e presentation sono organizzati per layer, non per feature: dentro domain convivono lo scaffolding generico e circa 25 verticali di business (fatturazione elettronica, adempimenti fiscali, moduli EU AI Act, e altri). Un filtro per path avrebbe trascinato tutta quella logica proprietaria dentro un repo pubblico.

C'e anche un secondo motivo, meno ovvio: git filter-repo esegue un reset --hard sul working tree corrente, non solo sul clone di destinazione. Lanciarlo nella cartella di lavoro viva, con modifiche non ancora committate, significa perderle. Per un template la scelta corretta e la curatela manuale file per file dentro una cartella nuova, con una storia git fresca e minima: nessuna storia del monorepo preservata, quindi nessun rischio di esporre contenuti o commenti che erano stati rimossi in commit successivi ma che sopravvivono nella cronologia.

Il gate ArchUnit che gira davvero (e non zero test)

Il cuore del template e il file CleanArchitectureTest. La sua unica ragione di esistere e fallire il build quando qualcuno viola la direzione delle dipendenze: la freccia deve andare verso il dominio (presentation, poi application, poi domain, con infrastructure che dipende dal dominio, mai il contrario). Ma qui c'e la trappola che ha dato origine all'intero repo.

Nel backend reale, per mesi, il gate ArchUnit e stato verde senza proteggere nulla. Il motivo: scritto con il pattern idiomatico @AnalyzeClasses + campi statici @ArchTest, l'engine archunit-junit5 non veniva scoperto da Surefire contro la versione di JUnit Platform che Spring Boot 3.4 porta con se. Risultato: Tests run: 0. Un badge verde su una regola che non veniva mai eseguita. La violazione l'abbiamo scoperta solo introducendo di proposito una dipendenza vietata e vedendo il build restare verde.

Confronto tra il pattern ArchUnit a campi statici e il pattern a metodi @Test espliciti
Pattern del test ArchUnit Regole eseguite Esito
@AnalyzeClasses + campi @ArchTest 0 verde per finta (non protegge nulla)
@Test + ClassFileImporter (questo template) 4 gate reale, fallisce sulle violazioni
Lo stesso set di regole con due pattern di scrittura: solo il secondo viene davvero eseguito da Surefire con lo stack di Spring Boot 3.4.

La correzione e semplice ma non ovvia: niente campi @ArchTest. Si importano le classi una volta in @BeforeAll con ClassFileImporter, e ogni regola diventa un normale metodo @Test che chiama rule.check(importedClasses). Questi metodi Jupiter vengono scoperti come qualsiasi altro unit test. La verifica di chiusura e sempre la stessa: mvn test -Dtest=CleanArchitectureTest deve stampare Tests run: 4, mai Tests run: 0.

I trade-off dichiarati di un template "broad"

Ho scelto una estrazione ampia invece di uno scheletro minimo con una sola slice. Il costo e reale: piu superficie da mantenere, piu dipendenze (JWT, Flyway, Resilience4j, rate-limit) che un progetto minimale non avrebbe. Il beneficio e che il template mostra come i layer interagiscono in un caso non banale, non solo in un CRUD giocattolo. Un lettore che vuole solo la struttura pura puo cancellare la slice JWT o il rate-limit in cinque minuti; il contrario, cioe aggiungere un pattern realistico a uno scheletro troppo scarno, richiede molto piu lavoro e lascia dubbi su come cablarlo correttamente rispetto ai layer.

Il secondo trade-off e sui segreti. Il template spedisce un .env.example con placeholder e un application.yml che legge tutto da variabili d'ambiente, con default esplicitamente marcati come non-produzione. Nessuna chiave di firma HS256 reale, nessuna credenziale di database, nessun IP di produzione: un secret-scan a base di grep gira prima del primo push e deve uscire con codice 0. E una difesa deliberatamente semplice, non un vault: per un template pubblico la barra e "nessun segreto reale nel tree", non "gestione avanzata dei segreti".

A chi serve, e a chi no

Se stai avviando un nuovo servizio Spring Boot e vuoi partire con una separazione dei layer gia forzata da un test, questo template ti fa risparmiare la parte noiosa e insidiosa: il cablaggio dei port, l'adapter di persistenza che traduce fra dominio e JPA, il filtro JWT stateless, e soprattutto il gate ArchUnit scritto nel modo che viene davvero eseguito. Se invece cerchi un framework opinato con generazione di codice, scaffolding CLI e moduli pronti, questo non fa per te: e volutamente piccolo, leggibile in un pomeriggio, e pensato per essere letto e modificato, non usato come dipendenza.


Nella serie «Original Research», precedente: 04 - INP e Core Web Vitals: da 59 a 88.

Domande frequenti

Perche un test ArchUnit con campi @ArchTest puo essere verde ma non eseguire nulla?

Perche l'engine archunit-junit5 non viene scoperto da Surefire contro la versione di JUnit Platform che Spring Boot 3.4 porta con se: i campi statici @ArchTest non vengono raccolti come test e il risultato e Tests run: 0, un verde che non protegge nulla. Il template evita il problema usando metodi @Test espliciti con ClassFileImporter, che vengono scoperti come qualsiasi altro unit test.

Perche non hai usato git filter-repo per estrarre il template dal monorepo?

Per due motivi. Primo: i layer del monorepo mescolano lo scaffolding generico con circa 25 verticali di business proprietari nella stessa gerarchia di package, quindi un filtro per path avrebbe trascinato codice proprietario in un repo pubblico. Secondo: git filter-repo esegue un reset --hard sul working tree corrente, con il rischio di distruggere lavoro non committato. La curatela manuale file per file con storia git fresca e piu sicura.

Il template richiede un database per compilare e passare i test?

No. I test del dominio e il gate ArchUnit non caricano il contesto Spring, quindi mvn test passa su JDK 21 senza database e senza rete. Il database Postgres e la migrazione Flyway servono solo quando avvii davvero l'applicazione con mvn spring-boot:run.

Come faccio a essere sicuro che il gate stia davvero girando?

Lancia mvn test -Dtest=CleanArchitectureTest e verifica che l'output riporti Tests run: 4 (non Tests run: 0). Come controprova, introduci di proposito una dipendenza vietata, per esempio importa una classe di infrastructure dentro un controller di presentation: il build deve fallire. Poi annulla la modifica.

Fonti e approfondimenti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti