Il modulo 7 ha costruito un quadro di come i team trasformano l’architettura in sistemi che girano davvero. Le strategie di branching nella lezione 49, il trunk-based development nella lezione 50, il CI per pipeline di dati nella lezione 51, i pattern di CD nella lezione 52, l’infrastructure as code nella lezione 53, i container nella lezione 54, Kubernetes nella lezione 55. Ogni lezione è uno strato; ogni strato interagisce con gli altri. Lo scopo di questo case study di chiusura è guardare a una sola azienda che ha messo insieme tutti quegli strati a scala e ha pubblicato abbastanza materiale al riguardo da rendere il quadro concreto.
Stripe è la storia canonica dell‘“engineering ad alto rischio con alta frequenza di deploy”. Gestiscono l’infrastruttura finanziaria che processa centinaia di miliardi di dollari all’anno. Il sistema deve essere disponibile (un pagamento che fallisce perché Stripe è down è una transazione persa vera per un merchant vero) e sicuro da deployare (un payment processor con un bug è peggio di uno lento). Quei due requisiti tirano in direzioni opposte per la maggior parte dei team. Le pratiche pubblicate da Stripe mostrano come lo stesso team possa colpire entrambi i bersagli, e cosa abbiano investito per renderlo possibile.
Le informazioni in questa lezione vengono dall’engineering blog di Stripe e da una manciata di talk pubblici. Le citazioni in fondo riportano le fonti primarie. La cornice è mia; le pratiche sono loro.
La forma del problema
Stripe sta nel payment path di una larga frazione del commercio internet. Quando un buyer clicca “paga” su uno store Shopify, su una corsa Uber, su un abbonamento SaaS, la richiesta spesso colpisce l’API di Stripe. Il latency budget è breve (un pagamento che ci mette troppo sembra rotto al buyer), il correctness budget è ancora più breve (una carta addebitata in modo sbagliato o un’autorizzazione mancata è un incidente di customer-service nel migliore dei casi e una grana regolatoria nel peggiore), e il volume è enorme.
Il team che gestisce questo sistema ha anche le pressioni normali di qualsiasi azienda tech in crescita: nuove feature, nuovi metodi di pagamento, nuovi paesi, nuove regolamentazioni, nuovi pattern di frode. Devono rilasciare costantemente. I deploy non possono fermarsi, i bug non possono finire in produzione, e il sistema non può andare giù. La domanda interessante è cosa abbiano costruito per rendere possibili tutte e tre le cose insieme.
Monorepo con servizi
Gran parte del codice di Stripe vive in un singolo repository. Non è la stessa cosa di un monolite. Il repository contiene molti servizi, deployati in modo indipendente, con rotazioni di on-call indipendenti. Quello che il monorepo offre è una singola fonte di verità per il codice, un singolo grafo di dipendenze, una singola configurazione CI da mantenere.
I benefici che Stripe ha citato pubblicamente:
I refactor cross-service sono trattabili. Rinominare un’API in un servizio e aggiornare ogni caller in altri dodici servizi è una singola pull request. In un setup multi-repo lo stesso cambiamento è un rollout coordinato attraverso tredici repo, che in pratica vuol dire che non avviene e l’API resta con il nome sbagliato per anni.
Internal API e contract testing sono diretti. I test del servizio A possono tirare su il servizio B dalla stessa checkout. I contratti tra servizi vengono testati a ogni commit, non al momento dell’integrazione.
L’investimento in tooling è messo a frutto. L’infrastruttura CI, la build cache, la logica di test selection, la static analysis, i linter: ognuno di loro è costruito una volta e usato attraverso ogni servizio. Il costo di scrivere un nuovo check viene ammortizzato sull’intera azienda.
Il trade-off è reale. Un monorepo della scala di Stripe ha il proprio sforzo ingegneristico dietro. Build system (stile Bazel o custom), test selection (eseguire solo i test toccati dal cambiamento), parallelismo CI, tooling per la code review: tutto deve essere tarato sulla taglia. Per un piccolo team sarebbe drammaticamente sproporzionato. Per un’azienda che è cresciuta dentro la scala, l’investimento è ciò che rende il monorepo praticabile.
La lezione per i team più piccoli non è “usate un monorepo”. È “i confini dei vostri repo modellano i tipi di cambiamenti che potete fare facilmente”. Un team che può fare refactor cross-service banalmente avrà un’architettura più pulita tre anni dopo rispetto a un team che non può.
Sorbet: i tipi come scelta architetturale
Il linguaggio di backend principale di Stripe era Ruby. Ruby è dinamicamente tipato di default, il che va bene a piccola scala e diventa caro a grande scala: un typo nel nome di un metodo non viene preso fino in produzione, un refactor che si dimentica un caller non è visibile fino a quando il caller crasha, un cambio di una struttura dati non ha alcun controllo automatico che il codice a valle continui a funzionare.
La risposta di Stripe è stata costruire Sorbet, un type checker graduale per Ruby, e renderlo open-source. L’investimento è significativo: uno sforzo pluriennale di un team dedicato, integrato in profondità nel workflow di sviluppo. Il payoff è una classe di bug presi a edit-time invece che in produzione.
L’inquadramento conta per questo modulo. I sistemi di tipi vengono a volte presentati come una preferenza di stile di codice: ad alcune persone piacciono, ad altre no. Alla scala di Stripe, i sistemi di tipi sono una scelta architetturale. Abilitano refactor che altrimenti sarebbero pericolosi. Rendono il codice più leggibile per chi fa review. Prendono il tipo di regressione che un test automatico non vedrebbe, perché il bug è in codice che non ha test.
La lezione si generalizza. Static analysis, type checking, linter, contract check: più di questi girano a edit-time e a CI-time, più il team può muoversi veloce senza rompere cose. Stripe ha costruito Sorbet perché non poteva comprare quello che le serviva; per la maggior parte dei team, gli strumenti off-the-shelf (TypeScript, mypy, il compilatore di Go) coprono lo stesso terreno.
Iterazione rapida con sicurezza
La frequenza di deploy a Stripe è alta. Molti deploy al giorno per servizio sono la norma. Il pezzo culturale è che ogni deploy è piccolo e reversibile.
I deploy piccoli sono più facili da rivedere e più veloci da fare rollback. Se un deploy contiene un singolo flip di feature flag e una piccola correzione di bug, e qualcosa va storto, la causa è in uno di due posti. Se un deploy contiene quaranta pull request mergiate, la causa è in uno di quaranta posti, e il rollback fa tornare quaranta cambiamenti che potrebbero ora dover essere rigiocati.
I deploy reversibili sono la proprietà che permette al team di trattare un deploy come un non-event. Un deploy che non si può rollbackare è un deploy che deve essere perfetto, e un deploy che deve essere perfetto produce una cultura di paura attorno ai deploy. L’investimento in reversibilità (feature flag, blue-green, canary, schema migration accurate) è ciò che rimuove quella paura.
Il collegamento alla lezione 50 è diretto. Trunk-based development con commit piccoli, gated da CI trunk-based, con feature flag per il lavoro incompleto: è il pattern che produce deploy piccoli e reversibili a scala.
Migrazioni online: la parte più dura
Il post pubblicato da Stripe “Online Migrations at Scale” è, nell’opinione di chi scrive, il riferimento canonico per “come cambiare uno schema critico senza downtime”. Vale la pena leggerlo per intero; la struttura del playbook è insegnabile e si applica ben oltre Stripe.
Il pattern che descrivono ha quattro fasi:
Cambio additivo backwards-compatible. Il primo deploy aggiunge la nuova colonna o la nuova tabella. Niente legge ancora da lì. Il vecchio code path continua a funzionare. Questo deploy può essere rollbackato banalmente perché niente dipende dalla nuova forma.
Dual writes. Il secondo deploy fa scrivere all’applicazione sia sulla vecchia che sulla nuova forma. La nuova forma viene popolata per le righe nuove; la vecchia forma è ancora autoritativa per le letture. Se qualcosa va storto, il team spegne il dual write e il sistema torna allo steady state precedente.
Backfill. Le righe storiche devono essere migrate alla nuova forma. È un job che gira offline, in batch, con rate limiting in modo da non sopraffare il database. Il job è idempotente (territorio della lezione 38), quindi può essere messo in pausa, ripreso e fatto ripartire senza produrre dati sbagliati.
Cutover. Una volta che la nuova forma è completamente popolata e i dual write sono stati confermati corretti, l’applicazione comincia a leggere dalla nuova forma. Questo è lo step a più alto rischio; viene gated da feature flag e fatto rollout gradualmente. La vecchia forma viene ancora scritta in caso di rollback.
Cleanup. Alla fine, dopo settimane di costruzione di fiducia, il dual write viene rimosso e la vecchia forma droppata.
L’intero processo può richiedere mesi per una singola migrazione. Non è un bug. La migrazione sta cambiando la forma di dati che sono critici per un sistema finanziario. La lentezza è il prezzo di farlo senza downtime e senza perdita di dati.
La lezione per qualsiasi team che gestisce dati persistenti è che le schema migration sono la parte più dura del CI/CD. La maggior parte dei team investe sotto-soglia qui, e la maggior parte degli outage che originano da deploy hanno la forma di una migrazione: un rinomino di colonna che ha rotto le letture, un NOT NULL aggiunto prima che il backfill finisse, una foreign key aggiunta con righe che la violano. Il playbook di Stripe è insegnabile, generalizzabile, e merita di essere trattato come template di default.
flowchart TB
OLD[Old schema in use] --> ADD[Phase 1: Add new shape]
ADD --> DUAL[Phase 2: Dual writes]
DUAL --> BACKFILL[Phase 3: Backfill historical]
BACKFILL --> CUT[Phase 4: Cutover reads]
CUT --> CLEAN[Phase 5: Drop old shape]
Investimento in observability
Un team che deploya molte volte al giorno deve sapere entro pochi secondi se il deploy ha peggiorato le cose. È un problema di observability.
Stripe ha investito pesantemente qui. Hanno costruito Veneur, un’implementazione statsd ad alto volume, quando gli strumenti off-the-shelf non riuscivano a tenere il passo con il loro volume di metriche. Hanno pubblicato sull’infrastruttura di tracing, sul logging strutturato, e sulla disciplina di trattare l’observability come una preoccupazione di engineering di prima classe.
Il caso d’uso a deploy-time è il più diretto. Quando una nuova versione fa rollout, le dashboard mostrano error rate, latenze e business metric per quella versione specifica. Se qualcosa regredisce rispetto alla vecchia versione, il rollout viene messo in pausa o fatto rollback automaticamente. Il team che ha fatto il deploy viene chiamato in pager, ma il sistema è già tornato a uno stato known-good.
Il caso d’uso più profondo è leggere il sistema in produzione. Un pagamento che ha impiegato troppo tempo ha una trace che mostra ogni servizio toccato, ogni query a database fatta, ogni API esterna chiamata. Il debugging è leggere la trace, non tirare a indovinare.
La lezione si generalizza. L’investimento in observability è ciò che rende sicura l’alta frequenza di deploy. Senza, il team vola alla cieca, e “deploy as non-event” si trasforma in “deploy as occasional disaster”. Con, il deploy è solo un altro data point sulle dashboard, e le dashboard rendono i problemi visibili prima degli utenti.
Deploy come non-event
Il pezzo culturale è il più difficile da copiare e il più importante da capire. A Stripe, i deploy non sono celebrati e non sono temuti. Avvengono in continuazione. L’ingegnere che ha rilasciato una feature non sta lì a guardare il deploy; il sistema lo gestisce, le metriche lo confermano, e l’ingegnere è già sulla cosa successiva.
È uno stato che un team deve guadagnarsi. Viene da anni di investimento in tooling, testing, observability e infrastruttura di rollback. Viene anche da una cultura che tratta i deploy come il modo normale in cui il sistema resta vivo, non come eventi rischiosi che richiedono cerimonia.
I team che non hanno questa cultura producono spesso il suo opposto: i deploy sono rari, paurosi, e arrivano con elaborati rituali di change-control. Ogni deploy è un grosso batch di cambiamenti accumulati, il che lo rende più probabile da rompere, il che produce più cerimonia, il che rende i deploy più rari, il che rende il prossimo deploy più grosso. Il circolo vizioso è reale.
La via d’uscita è il pattern di trunk-based development dalla lezione 50, più l’investimento in CI/CD dalle lezioni 51 e 52, più l’investimento in observability per sapere se le cose stanno funzionando. Non c’è scorciatoia. Non c’è nemmeno alternativa, se il team vuole rilasciare veloce e non rompere cose.
Cosa significa per il resto di noi
Stripe sta operando a una scala dove le loro soluzioni sono a volte sovradimensionate per quello che serve alla maggior parte dei team. Sorbet è un buon esempio: lo strumento giusto per una codebase Ruby della taglia di Stripe, esagerato per una startup di trenta persone la cui codebase Ruby è di cinquantamila righe. La lezione non è “costruite Sorbet”. La lezione è “investite nello strato di tooling che prende la vostra classe di bug più costosa a edit-time”.
Diversi pattern si generalizzano in modo più diretto:
L’investimento in tooling ripaga quando la frequenza di deploy del team è bottleneckata da lavoro manuale. Se ogni deploy ha bisogno di un umano che lo coccoli, la frequenza di deploy del team è tappata dall’attenzione umana. Automatizzare via l’umano rivela il bottleneck successivo.
I sistemi di tipi e la static analysis sono una scelta architetturale, non solo una preferenza di stile di codice. Prevengono classi di bug prima che colpiscano la prod. La decisione di usare un linguaggio tipato o di aggiungere tipi a un linguaggio dinamico è una decisione su come scala il team.
Le schema migration sono la parte più dura del CI/CD. Il playbook di online migration di Stripe è il riferimento canonico. I team che adottano il pattern presto hanno molti meno incident a forma di migrazione rispetto ai team che improvvisano ogni migrazione.
La fiducia nella test suite abilita la velocità. Se la CI è affidabile, il team si fida di una build verde e rilascia. Se la CI è flaky, il team impara a ignorarla, e la rete di sicurezza smette di funzionare. L’investimento nell’affidabilità della CI non è glamour ed è ad alta leva.
La cultura conta tanto quanto il tooling. I deploy sono eventi perché il team li rende eventi. Lo shift culturale verso “i deploy sono routine” richiede sia il tooling per renderli sicuri che l’accordo sociale per trattarli come routine.
Chiusura del modulo 7
Le sette lezioni di questo modulo sommano a un singolo quadro operativo. Le strategie di branching e il trunk-based development decidono come il team si coordina attorno alla codebase. La CI prende i bug prima del merge. Il CD limita il blast radius dei bug che sfuggono. L’infrastructure as code rende gli ambienti riproducibili e revisionabili. Container e Kubernetes sono il substrato runtime su cui vive tutto il resto. Il case study di Stripe mostra come si presenta tutto questo quando in tutti e sette gli strati si investe insieme, in un contesto dove il costo di sbagliare è alto.
Il modulo 8 parte con l’orchestration. Lo strato più profondo di come i job in una piattaforma dati vengono effettivamente schedulati, come vengono tracciate le dipendenze tra di loro, e come il team gestisce la piattaforma una volta che i pattern di deploy sono in posto. Airflow, Dagster, Prefect, e i pattern che funzionano attraverso tutti loro.
Citazioni
- “Online Migrations at Scale” sull’engineering blog di Stripe (
https://stripe.com/blog/online-migrations, consultato 2026-05-01). - “Sorbet: Stripe’s type checker for Ruby” sull’engineering blog di Stripe (
https://stripe.com/blog/sorbet-stripes-type-checker-for-ruby, consultato 2026-05-01). - Documentazione del progetto Sorbet (
https://sorbet.org/, consultato 2026-05-01). - “Veneur: a high-performance, distributed statsd” sull’engineering blog di Stripe (referenziato via
https://stripe.com/blog/engineering, consultato 2026-05-01). - Indice dell’engineering blog di Stripe (
https://stripe.com/blog/engineering, consultato 2026-05-01). - Sito Trunk Based Development, referenziato dalla lezione 50 (
https://trunkbaseddevelopment.com/, consultato 2026-05-01).