Hai mai guardato una base di codice che hai scritto anni fa, e il tuo unico pensiero è: “Ma a cosa stavo pensando?”
Nel 2020 mi stavo tuffando a capofitto in Kubernetes e nel mondo cloud-native per costruire un sistema di notifiche. Ero così entusiasta di poter finalmente distribuire la mia app nel cloud, ed ero determinato a renderla la migliore possibile.
Avevo molta esperienza con i monoliti e architetture “tradizionali”, e avevo lavorato anche con alcuni sistemi distribuiti in precedenza, ma sempre all’interno di un team di ingegneri esperti.
Questa volta ero l’unico in azienda con una certa esperienza nei microservizi e mi sentivo come un vero e proprio cloud architect. Oggi, vedo un esempio da manuale di sovra-ingegnerizzazione (over-engineering) dei microservizi e caos dovuto alla concorrenza.
Ecco una retrospettiva approfondita sul debito tecnico che ho accumulato, sul codice che oggi non rilascerei mai, e sulle lezioni che mi sono costate ore di debug in reperibilità.
1. L’illusione del “Gateway Personalizzato”
Uno dei componenti più pesanti del progetto era un API gateway personalizzato. Inizialmente pensavo di stare costruendo un semplice reverse proxy e di non aver bisogno di usare alcun framework o soluzioni esistenti come NGINX o Envoy, ma mi resi presto conto che stavo costruendo un gateway completo con autenticazione, rate limiting e inoltro delle richieste.
Il Verdetto:
Stavo risolvendo problemi di infrastruttura tramite il codice dell’applicazione. Costruendo un gateway personalizzato, ho creato un enorme onere di manutenzione per un set di funzionalità che NGINX o un servizio gestito come Google API Gateway avrebbero potuto gestire in modo nativo. La complessità risiedeva nel mio codice invece di essere gestita dalla piattaforma.
La Lezione del 2026:
Non scrivere ciò che puoi affittare (o usare gratis). L’infrastruttura dovrebbe essere trasparente. Se ti ritrovi a scrivere codice boilerplate complesso solo per spostare byte da A a B, probabilmente stai sovra-ingegnerizzando la soluzione.
2. Concorrenza senza Limiti: Il Memory Leak in Go
Nel servizio di notifica, ho utilizzato un channel Go standard per elaborare i messaggi in arrivo da Pub/Sub. Sulla carta sembrava perfetto. Nella realtà, era una bomba a orologeria.
L’Errore del 2020:
Il Refactoring del 2026:
Oggi userei un pool di worker limitato per garantire che il sistema rimanga stabile sotto carico, indipendentemente dal numero di messaggi nella coda.
|
|
3. L’implementazione dell’“Happy Path”
Ho utilizzato una coda di messaggi (Pub/Sub) per disaccoppiare i servizi, che era un passo nella giusta direzione. Tuttavia, l’implementazione era incredibilmente fragile perché dava per scontato l’“Happy Path” (il percorso in cui va tutto bene).
L’Errore del 2020:
Il Verdetto:
Il sistema mancava di qualsiasi concetto di pattern di resilienza. Se il provider a valle falliva, i dati semplicemente svanivano nel nulla.
- Nessun backoff incrementale: I tentativi (retry) non venivano gestiti, o avvenivano istantaneamente, martellando servizi già in fallimento.
- Nessuna Dead Letter Queue (DLQ): I messaggi falliti non andavano da nessuna parte per l’ispezione manuale.
Il Refactoring del 2026:
Un’implementazione senior si aspetta il fallimento. Usiamo il backoff esponenziale e una DLQ per garantire l’integrità dei dati.
|
|
4. Cecità alle Risorse in GKE
Ho distribuito l’app su GKE senza configurare un singolo limite o richiesta di risorse nei miei manifest YAML.
Il Verdetto:
Davo per scontato che Kubernetes fosse abbastanza “intelligente” da gestire le risorse. Poiché il mio binario Go era piuttosto pesante, un singolo nodo spesso rimaneva “affamato” di risorse, portando a fallimenti a cascata in tutto il cluster. Stavo trattando un cluster condiviso come una VM dedicata con risorse infinite.
La Lezione del 2026:
Le richieste e i limiti delle risorse non sono negoziabili. In un ambiente cloud-native, devi dire allo scheduler esattamente di cosa hai bisogno. Questa è la differenza tra un sistema stabile e uno che si riavvia ogni volta che c’è un piccolo picco di traffico.
Conclusione: La Semplicità è una Caratteristica da Senior
Fortunatamente il sistema non ha mai dovuto gestire condizioni di traffico elevato, ma gli occasionali picchi rendevano il cluster instabile, e ho dovuto affrontare le conseguenze del mio over-engineering.
Il progetto è stato un fallimento a livello architettonico, ma un successo per la mia formazione. Ho imparato molto su k8s, sul cloud-native e sui pattern di concorrenza in Go. Mi ha insegnato che un “Senior” non è qualcuno che sa usare ogni funzionalità di Kubernetes, ma qualcuno che sa quando mantenere le cose semplici.