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.
| 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 |
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.
Commenti
Caricamento commenti...
Accedi per commentare