La lezione precedente ha messo in fila i sintomi che ti dicono quando l’architettura a singolo server non basta più. Il messaggio implicito era rassicurante: l’architettura che hai costruito nelle lezioni da 1 a 6 va bene fino a quando non va più bene, e passeremo il resto del corso a insegnarti come farla evolvere.
Questa lezione è l’altra metà del messaggio. La maggior parte dei team over-engineera la propria prima architettura. Si lanciano sui microservices, sui database distribuiti, su Kubernetes, sulle service mesh, sull’event sourcing, sul CQRS, tutto prima di avere il carico per giustificare alcunché di tutto questo. Il risultato è anni di carico operativo per capability che non usano, talento che non riescono ad assumere, e complessità che non riescono a debuggare alle tre di notte.
Tre aziende sono utili qui, perché tutte e tre hanno resistito alla moda, in pubblico, con resoconti tecnici dettagliati, e a scale molto più grandi della tua. Stripe è rimasta su Postgres molto più a lungo di quanto l’industria si aspettasse. Shopify ha portato un monolith Rails fino al range dei merchant da miliardi di dollari. Basecamp ci ha scritto sopra un manifesto.
Se la tua prima reazione è “beh, sono casi speciali”, il resto di questa lezione è per te. La maggior parte delle architetture sono casi speciali. La domanda è se la tua sia speciale in un modo che giustifichi la complessità, o se tu sia, come la maggior parte dei team, a copiare un pattern costruito per risolvere un problema che non hai.
Caso 1: Stripe e la lunga scommessa su Postgres
Stripe processa pagamenti per una frazione significativa del commercio di internet. È un workload real-time, che muove soldi, sensibile alla latency, pesante in audit, regolato finanziariamente. Secondo la conventional wisdom dell’era 2015-2020, è esattamente il tipo di workload che dovresti mettere su un data store distribuito custom-built. NoSQL, sharded, eventually consistent, con un coordination layer fatto in casa.
Non è quello che ha fatto Stripe. Stripe ha fatto girare, e al momento in cui scrivo nel 2026 fa ancora girare in larga parte, il grosso dei suoi dati transazionali di produzione su un piccolo insieme di istanze Postgres curate con attenzione. Sharded, sì, con un loro sharding layer sopra. Pesantemente tunate. Replicate tra availability zone. Ma sempre Postgres. Sempre SQL. Sempre ACID. Sempre un database relazionale che un database administrator del 2005 riconoscerebbe.
Due post di engineering valgono la pena di essere letti per intero per capire la scommessa. Il primo è “Online Migrations at Scale” (https://stripe.com/blog/online-migrations), che descrive i pattern che Stripe usa per far evolvere uno schema Postgres mentre serve miliardi di dollari di traffico. È il complemento pratico al sintomo 6 della lezione precedente. Il secondo è la serie di talk “Ringpop, sharding, and database operations” che il team dati di Stripe ha tenuto a varie conferenze tra il 2018 e il 2020, che descrive lo sharding layer che hanno costruito sopra Postgres invece che sotto.
Le lezioni che escono dalla storia di Stripe:
- Postgres alla giusta dimensione, con la giusta cura, regge un carico enorme. Il soffitto è molto più alto di quanto la maggior parte dei team assuma, perché la maggior parte dei team non ha mai visto un’istanza Postgres tunata come si deve, con hardware adeguato, query sensate, buoni indici, e un operatore competente. Hanno visto solo la versione troppo piccola per il lavoro.
- Il costo di lasciare il modello relazionale è enorme. Rinunci a SQL, transazioni, join, foreign key, all’ecosistema ricco di tool, alla saggezza accumulata in quarant’anni di database administration, e alla possibilità di assumere ingegneri che già sanno come funziona. In cambio ottieni write throughput al costo della consistency. Per la maggior parte dei workload, è un brutto trade.
- Il costo di investire in scaling verticale e in buone database operation è piccolo in confronto. Un database engineer senior è caro ma non quanto il team che ti servirebbe per operare un distributed store custom.
La mossa di Stripe più difficile da copiare è la disciplina. Hanno tenuto il sistema semplice anche quando era di moda renderlo complicato, e hanno investito nel lavoro noioso e poco glamour di far funzionare Postgres alla loro scala. Il risultato è una payments platform che, in termini di complessità architetturale, somiglia più a una startup del 2010 che a un unicorno del 2020.
Caso 2: Shopify e il modular monolith
Shopify è una piattaforma di e-commerce. Ospita milioni di merchant, processa centinaia di miliardi di dollari di gross merchandise volume all’anno, e regge il traffico del Black Friday che per un weekend all’anno supera il picco annuale della maggior parte degli altri siti web. È, per qualunque misura, un sistema distribuito su larga scala.
È anche, strutturalmente, un monolith Rails. O meglio, lo è stato per gran parte della sua vita, e il passaggio da “monolith” a “modular monolith” è stato un percorso deliberato, lento, pubblico, ed educativo. Il riferimento canonico è il post di Shopify Engineering “Deconstructing the Monolith: Designing Software That Maximizes Developer Productivity” (https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity).
L’argomento di Shopify è grosso modo questo: il problema con un monolith non è che il codice vive in un solo repository o gira in un solo processo. Il problema è quando il codice manca di confini interni. Un monolith “big ball of mud”, dove qualunque modulo può chiamare qualunque altro modulo, dove le concerns di dominio si sparpagliano tra i file, dove un cambiamento al billing richiede un cambiamento allo shipping, è genuinamente difficile da scalare, sia tecnicamente che organizzativamente. Ma quella non è una proprietà dei monolith. È una proprietà del codice scritto male.
Un monolith modulare mantiene il deployment semplice (una app, un process group, una deploy pipeline) imponendo allo stesso tempo confini interni che assomigliano molto ai confini di servizio. Shopify usa internamente Ruby gem, con API pubbliche esplicite, e tratta la comunicazione tra moduli come se fosse una chiamata di rete anche quando è solo una method invocation. Il risultato è che i moduli possono essere ragionati in modo indipendente, possono evolvere in modo indipendente, e alla fine, se necessario, possono essere estratti in servizi separati. Ma finché quella necessità non arriva, il team gode della semplicità operativa di un solo deployment.
Le lezioni:
- Il monolith è una scelta di deployment, non un peccato di organizzazione del codice. Puoi avere un deployment monolitico con una struttura modulare pulita, oppure un deployment a microservices con un domain model intricato. Il secondo è molto peggio.
- I microservices senza disciplina sono più lenti e più soggetti a errori di un monolith senza disciplina. La parte “senza disciplina” sta facendo molto lavoro in quella frase. La maggior parte dei team non ha la disciplina che i microservices richiedono, perché i microservices richiedono una maturità organizzativa (indipendenza dei team, ownership, rotazione on-call, observability, infrastruttura di deployment) che la maggior parte dei team non ha ancora costruito.
- Il momento giusto per estrarre un servizio è quando hai misurato un dolore che un servizio risolverebbe. Shopify ha estratto servizi. Lo ha fatto per ragioni specifiche: un componente di checkout che doveva scalare in modo indipendente, un real-time inventory store che richiedeva garanzie di consistency diverse, un payment path che richiedeva isolamento. Non hanno estratto per moda.
La storia di Shopify è l’antidoto all’argomento “siamo troppo grandi per un monolith”. Sono più grandi di te, erano più grandi di te quando hanno preso la decisione, e la decisione è stata: tenere il monolith, investire in modularità.
Caso 3: Basecamp e il majestic monolith
Se Stripe è il caso di engineering e Shopify è il caso architetturale, Basecamp è il caso filosofico. David Heinemeier Hansson, il creatore di Ruby on Rails e cofondatore di 37signals, ha scritto un saggio nel 2016 intitolato “The Majestic Monolith” (https://m.signalvnoise.com/the-majestic-monolith/). È uno dei pezzi più citati e più fraintesi del canone architetturale. Leggilo per intero se non lo hai fatto.
L’argomento non è che i monolith siano universalmente corretti. L’argomento è che per un team di prodotto piccolo (37signals aveva circa una dozzina di ingegneri quando il post è stato scritto), l’overhead cognitivo di un sistema distribuito cancella qualunque guadagno di throughput tu possa ottenere scalando in larghezza. Un team di dodici ingegneri che fa girare un monolith Rails può spedire prodotto più veloce, debuggare più veloce, fare onboarding più veloce, e operare il sistema a costo più basso degli stessi dodici ingegneri che fanno girare quindici microservices. La matematica del throughput non riguarda le macchine; riguarda gli umani.
Il post a volte viene letto come anti-microservices. Non lo è. È anti-microservices-prematuri. La distinzione conta. C’è un livello di carico, una dimensione di team, una complessità organizzativa, alla quale i microservices ripagano. L’errore è assumere di essere a quel livello quando non ci sei.
Le lezioni:
- L’architettura è vincolata dalla dimensione del team tanto quanto dal carico. Un team da due-pizze che fa girare dieci servizi sta spendendo più tempo in coordinamento operativo che in prodotto. Un team da due-pizze che fa girare un servizio ben organizzato sta spedendo.
- L’architettura più cara è quella più grande del tuo problema. La lezione di Stripe è che il semplice arriva lontano. La lezione di Shopify è che la modularità dentro al monolith cattura gran parte del beneficio dei servizi. La lezione di Basecamp è che per un team piccolo, l’architettura semplice è anche quella veloce.
- Refactor verso i servizi quando il dolore ti costringe a farlo, non quando i talk delle conferenze ti dicono di farlo.
La conclusione trasversale dei tre case study è la stessa. Differisci la complessità architetturale. Paga il costo operativo dei sistemi distribuiti solo quando il carico lo richiede. Il resto del tempo, investi nell’architettura semplice: query migliori, indici migliori, test migliori, deploy migliori, observability migliore. Quel lavoro si accumula. La complessità architetturale fine a sé stessa no.
Tre timeline su una pagina
flowchart LR
subgraph stripe ["Stripe (Postgres-first)"]
direction LR
s1["<b>2010</b><br/>Founded on Postgres"] --> s2["<b>2014</b><br/>Realtime fraud<br/>and ledger on PG"] --> s3["<b>2017</b><br/>Sharding layer<br/>on top of PG"] --> s4["<b>2020</b><br/>Online migration<br/>patterns shared"] --> s5["<b>2024</b><br/>Bulk of prod<br/>still on PG"]
end
subgraph shopify ["Shopify (Rails monolith)"]
direction LR
h1["<b>2004</b><br/>Founded as<br/>a Rails app"] --> h2["<b>2014</b><br/>Multi-billion GMV<br/>on the monolith"] --> h3["<b>2016</b><br/>Modular monolith<br/>strategy"] --> h4["<b>2019</b><br/>Deconstructing<br/>the Monolith"] --> h5["<b>2024</b><br/>Selective<br/>extractions"]
end
subgraph basecamp ["Basecamp and 37signals"]
direction LR
b1["<b>2004</b><br/>Basecamp<br/>on Rails"] --> b2["<b>2016</b><br/>Majestic Monolith<br/>essay"] --> b3["<b>2020</b><br/>HEY launched,<br/>also a monolith"] --> b4["<b>2024</b><br/>Small set of<br/>monoliths,<br/>dozen engineers"]
end
classDef yr fill:#1f2933,stroke:#0d9488,color:#e8edf1
classDef boundary fill:transparent,stroke:#0d9488,stroke-dasharray: 5 5
class s1,s2,s3,s4,s5,h1,h2,h3,h4,h5,b1,b2,b3,b4 yr
class stripe,shopify,basecamp boundary
Il punto visivo della timeline è che l’architettura semplice non è stata una fase breve prima dell’inevitabile rewrite. In tutti e tre i casi è durata, e dura ancora, ben oltre la scala alla quale la maggior parte dei team assume di doversi spostare. Il rewrite spesso non arriva. Quando arriva, è mirato, lento, e giustificato da un dolore specifico.
Cosa i case study non dicono
Vale la pena essere espliciti su cosa queste storie non dimostrano, perché il discorso intorno a loro tende a esagerare in entrambe le direzioni.
Non dimostrano che i monolith siano sempre corretti. Non lo sono. Ci sono workload (real-time bidding, serving read-heavy distribuito geograficamente, machine-learning inference all’edge) dove un singolo monolith genuinamente non può fare il lavoro, e dove un’architettura service-oriented o distribuita è il giusto punto di partenza. Quei workload sono più rari di quanto il discorso suggerisca, ma esistono.
Non dimostrano che Postgres sia sempre corretto. Un workload che è fondamentalmente analitico (terabyte di telemetria, query OLAP ad-hoc, aggregazioni fan-out) sta su un columnar store, non su Postgres. Un workload che è fondamentalmente key-value con alto write throughput e bassi requisiti di consistency sta su un key-value store. La lezione di Stripe è che i workload transazionali, anche a scala molto grande, possono restare su Postgres. Non è che tutti i workload dovrebbero.
Non dimostrano che i microservices siano cattivi. Dimostrano che i microservices sono cari, e che la spesa è raramente giustificata prima di soglie specifiche di scala e di organizzazione. Dopo quelle soglie, i microservices possono essere la scelta giusta. Shopify ne usa alcuni. Stripe ne usa alcuni. Il punto è che “alcuni” è molto più piccolo di “tutti”, e viene selezionato per necessità invece che per default.
Quello che i case study dimostrano davvero è un singolo pattern ripetibile: i team che hanno resistito alla complessità finché i sintomi non hanno forzato loro la mano hanno costruito sistemi che sono durati, hanno evoluto, e sono rimasti economici da far girare. I team che hanno adottato la complessità presto, per moda o per paura, hanno costruito sistemi che erano cari da far girare e lenti da cambiare. Il primo gruppo include Stripe, Shopify, e Basecamp. Il secondo gruppo è senza nome perché i post-mortem sono privati. Esistono. Magari ci hai lavorato.
Dove va il corso da qui in poi
Sei arrivato alla fine del Modulo 1. Il take-away di queste otto lezioni è, in una frase: parti semplice, riconosci i sintomi quando appaiono, e muoviti solo quando i sintomi forzano la mossa. L’architettura delle lezioni da 1 a 6 basta alla maggior parte dei team per la maggior parte della loro vita utile.
Ma “la maggior parte” non è “tutti”. Alcuni team genuinamente vanno oltre il singolo server, e la domanda diventa come far evolvere l’architettura senza peggiorarla. È di questo che parla il resto del corso, e il prossimo modulo parte dai fondamentali dei sistemi distribuiti: cosa cambia nell’istante in cui smetti di avere una sola macchina, perché i sistemi distribuiti sono più difficili di quanto sembrino, e quali modelli mentali ti servono prima di scrivere una sola riga di codice che parla con un’altra macchina su una rete.
Il Modulo 2 parte nella prossima lezione con una delle idee fondazionali del campo: le otto fallacie del computing distribuito. La lista ha trent’anni ed è ancora dolorosamente rilevante. La attraverseremo una fallacia alla volta, con esempi di sistemi che la gente ha costruito e che sono falliti perché hanno assunto che ogni fallacia fosse vera.
Stripe, Shopify, e Basecamp hanno differito quella complessità il più a lungo possibile. Passeremo le prossime lezioni a imparare cosa stavano differendo.
Citazioni e letture di approfondimento
- Stripe Engineering, “Online Migrations at Scale”,
https://stripe.com/blog/online-migrations(consultato 2026-05-01). - Shopify Engineering, “Deconstructing the Monolith: Designing Software That Maximizes Developer Productivity”,
https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity(consultato 2026-05-01). - David Heinemeier Hansson, “The Majestic Monolith”,
https://m.signalvnoise.com/the-majestic-monolith/(consultato 2026-05-01). - Talk di Stripe su sharding e operations, disponibili tramite il canale YouTube di Stripe Engineering e gli archivi delle conferenze dal 2018 al 2020.