Contenuti

Come preparo un’architettura per crescere senza riscrivere tutto dopo un anno

Come preparo un’architettura per crescere senza riscrivere tutto dopo un anno

/en/architettra_per_crescere/img.png

Se hai lavorato in una startup, in una PMI o in un team “sempre in emergenza”, lo sai già:
all’inizio vai veloce. Funziona. Spedisci. Chiudi ticket.
Poi dopo 6 mesi (a volte 3) ti accorgi che ogni feature nuova costa il doppio.
Dopo un anno ogni modifica è chirurgia a cuore aperto.

E spesso finisce con la frase che nessuno vuole dire ad alta voce:

“Ok, dobbiamo riscrivere tutto.”

Questo articolo è un antidoto pragmatico a quella situazione.

Non parlo da eroe senza macchia.
Sono il primo ad aver “spaghettificato”. Più di una volta.
E proprio perché ho pagato quel debito tecnico con interessi da usura, oggi ti dico:
non ti serve fare architetture perfette, ti serve fare architetture che reggono.

Con qualche abitudine semplice e un’ora in più ogni tanto, puoi risparmiarti centinaia di ore dopo.


Il problema: quando non hai tempo, “spaghettifichi” (e lo fai per buone ragioni)

La spaghettificazione non nasce perché siamo incompetenti.
Nasce perché siamo sotto pressione.

  • devi consegnare ieri
  • non hai tempo di mettere ordine
  • non sai se questa feature verrà usata davvero
  • il business ti cambia la priorità ogni 3 giorni
  • magari sei pure in sotto-organico

E quindi fai la cosa più umana del mondo:

  • metti la logica “dove capita”
  • duplichi un pezzetto di codice “perché ci metto 5 minuti”
  • fai una scorciatoia “tanto poi la sistemiamo”
  • i test? “dopo”

Il problema è che quel “poi” arriva sempre quando il sistema è più grande, più fragile e più costoso da cambiare.


L’obiettivo non è scalare: è evitare la riscrittura

Quando si parla di architettura “per crescere”, molti pensano subito a:

  • microservizi
  • event-driven
  • CQRS
  • Kubernetes
  • service mesh

Ma la maggior parte dei progetti non muore perché non ha Kubernetes.

Muore perché:

  • nessuno capisce dove sta la logica
  • ogni change rompe qualcosa di imprevedibile
  • il riuso è impossibile
  • fare refactoring costa troppo
  • onboarding nuovi dev = settimane di panico

Quindi il focus vero è:

rendere il sistema modificabile senza paura.


La regola d’oro: disaccoppiamento logico, non per forza “distribuito”

Qui c’è un concetto che salva progetti interi.

Disaccoppiare non significa “spezzare tutto in 10 servizi”.
Significa prima di tutto una cosa più semplice:

✅ Separare le responsabilità a livello logico

Esempio (molto comune):

  • Controller / Handler: parsing input e risposta
  • Use case / Service: logica applicativa
  • Repository / Gateway: accesso a DB e API esterne
  • Domain: regole e strutture centrali

Non è “architettura da libro”. È un modo per evitare che:

  • query SQL finiscano dentro i controller
  • logica di business sia sparpagliata in 12 file
  • chiamate esterne siano mischiate con validazioni e calcoli

Questo disaccoppiamento è quello che ti permette, dopo un anno, di fare:

  • refactoring senza riscrivere
  • test senza diventare matto
  • nuove feature senza rompere il resto

Il trucco che costa poco tempo: progettare i confini, non tutto il castello

Non serve disegnare un’architettura completa.

Serve mettere confini chiari dove la spaghettificazione esplode più facilmente.

I 3 confini “salva-progetto” sono:

1) Confine tra logica e infrastruttura

La logica applicativa non dovrebbe sapere:

  • se i dati arrivano da MySQL o Postgres
  • se stai chiamando un REST o un gRPC
  • se sei in sync o async

2) Confine tra dominio e UI/API

HTTP non deve diventare la tua architettura mentale. Le entità e i casi d’uso devono avere senso anche senza REST.

3) Confine tra moduli che evolvono a velocità diversa

Esempio: fatturazione e tracking spedizioni. Magari entrambi nel “prodotto”, ma cambiano con frequenze e rischi diversi.


Riutilizzo del codice: non copiare/incollare, estrai il concetto

Il riuso è un tema noioso.
Sembra una predica da community tech anni 2000.

Eppure il costo nascosto del copia/incolla è devastante, perché:

  • correggi un bug in un punto e lo lasci in altri 3
  • modifichi un flusso e ne rompi uno “gemello”
  • ogni feature diventa un giro turistico nel repo

La soluzione pragmatica non è “fare una libreria interna perfetta”.

È:

✅ creare punti di riuso molto semplici

Tipo:

  • package/module common solo per utilità stabili
  • componenti lib/ per codice condiviso tra moduli
  • funzioni “pure” (senza I/O) che sono testabili e riusabili

Un buon indicatore è questo:

Se lo stesso concetto lo scrivi 2 volte, probabilmente tra 1 mese lo scriverai 5.


Test “per quanto possibile”: non serve coprire tutto, serve coprire ciò che fa male quando si rompe

Altro tema ultra-trito: “bisogna fare i test”.

Sì, ma detta così non aiuta nessuno. Perché quando sei in pressione, la frase “fai i test” compete con “devo consegnare”.

Quindi la versione vera è:

✅ fai test dove riducono davvero il rischio

Priorità pratica:

  1. Test su funzioni pure
    roba che prende input → produce output
    (sono veloci, affidabili e costano poco)

  2. Test sui casi d’uso principali
    i flussi che generano soldi o disastri se falliscono

  3. Test su bug che hai già pagato
    se un bug ti è costato 6 ore una volta, metti un test e non lo rivedi più

E poi una frase che a me ha salvato la vita:

“Un test non serve a dimostrare che funziona.
Serve a farti cambiare codice senza paura.”


Repo unica vs multi repo: spesso monorepo è meglio (soprattutto all’inizio)

Tema delicato, perché c’è religione attorno.

Ma nella pratica, per tantissimi team piccoli e medi:

✅ una repo unica è più efficiente

Perché ti dà:

  • visibilità immediata del sistema
  • refactoring cross-modulo più semplice
  • un solo posto dove cercare
  • onboarding più veloce
  • versioning più lineare

Il multirepo ha senso quando hai davvero:

  • team indipendenti
  • rilasci indipendenti
  • ownership separate
  • policy di accesso diverse

Ma il multirepo troppo presto porta spesso a:

  • duplicazione di codice
  • dipendenze incrociate ingestibili
  • versioni non compatibili
  • “non posso cambiare X perché rompe Y che vive altrove”

Monorepo non è “più bello”. È solo più pragmatico finché il costo organizzativo di separare non è giustificato.


La cosa più importante: investire un’ora adesso per non spenderne cento dopo

Lo so: tutto quello che ho scritto sembra ovvio. Sembra roba detta da decenni.

Ma io continuo a vedere team interi (anche forti) fare fatica a:

  • fermarsi
  • respirare
  • dire “ok, investiamo un’ora in più”

E il problema è proprio lì: non è che non sappiamo cosa è giusto, è che non ci concediamo il tempo per farlo.

Io sono stato il primo a non farlo. E mi sono trovato poi a pagare settimane di dolore:

  • feature che richiedono giorni invece che ore
  • bug imprevedibili
  • paura di toccare pezzi delicati
  • refactoring rimandati fino alla riscrittura

Quindi se posso lasciarti 3 regole finali, sono queste:

  1. Separare la logica dall’I/O è la forma più economica di architettura
  2. Riuso = meno bug e meno lavoro invisibile
  3. Test piccoli e mirati battono “zero test” e battono anche “test perfetti mai finiti”

Non serve fare i puristi.
Serve fare scelte che ti lasciano respirare tra 6 mesi.

Perché crescere è bello…
ma crescere e dover riscrivere tutto ogni anno è una condanna.