Come progetto un database
12 marzo 2026

Ogni decisione di schema che prendi all'inizio di un progetto ha una vita utile di anni. Puoi rinominare una variabile in dieci secondi. Cambiare come è strutturata un'entità centrale nel tuo database — dopo che è in produzione, con dati reali, con query costruite attorno a quella struttura — è costoso in un modo che la maggior parte delle altre decisioni tecniche non lo è.
Questo rende il design del database uno dei pochi ambiti in cui vale la pena pensare con cura prima di scrivere la prima riga di codice. Non sovra-ingegnerizzare — pensare. C'è una differenza.
Parti dalle query, non dalle entità
L'istinto naturale quando si progetta un database è partire dalle cose del proprio dominio. Un'app per barbieri ha barbieri, clienti, servizi, appuntamenti. Un'app di messaggistica ha utenti, stanze, messaggi. Si disegnano box e relazioni tra loro. Sembra un buon design.
Il problema è che questo approccio ottimizza per come pensi ai dati, non per come li accederai. E il database non sa come pensi — conosce solo come interroghi.
La domanda che mi faccio prima di toccare uno schema è: quali sono le dieci query più frequenti che questa applicazione eseguirà? Non ipoteticamente — specificatamente. "Prendi tutti gli appuntamenti imminenti per un determinato barbiere, ordinati per orario." "Prendi gli ultimi 50 messaggi in una stanza, con i nomi dei mittenti." "Prendi il fatturato totale per cliente nel mese corrente."
Se riesci a scrivere quelle query, puoi progettare uno schema che le serve. Se non riesci, stai indovinando — e indovinare a livello di schema è costoso da correggere.
La trappola della normalizzazione
La teoria dei database relazionali spinge verso la normalizzazione: ogni dato in un posto solo, relazioni espresse tramite join. È intellettualmente pulita. In pratica, produce spesso schemi dolorosi da interrogare.
Uno schema completamente normalizzato per un'app di messaggistica potrebbe avere utenti, stanze, membership, messaggi e allegati come tabelle separate. Ogni lettura di messaggi richiede un join su tre o quattro di esse. Per un'app dove le letture avvengono costantemente e a volume, paghi quel costo ad ogni richiesta.
La denormalizzazione non è pigrizia. È un tradeoff deliberato: accetti ridondanza nei dati memorizzati per rendere le letture più veloci e semplici. In un database documentale come MongoDB, questo è spesso il default corretto. Incorporare il nome e l'avatar del mittente direttamente nel documento del messaggio significa una lettura invece di un join. Il costo è che se un utente cambia nome devi aggiornarlo in più posti — ma gli utenti cambiano nome raramente. Le letture avvengono ad ogni page load.
Modella per il caso comune, non per l'eccezione.
Embedding vs. referencing
In un database documentale, la domanda centrale di design è se incorporare dati correlati o referenziarli. La regola che uso: incorpora se leggi sempre i dati insieme; referenzia se li leggi indipendentemente.
In Barba Studio, un appuntamento include sempre i dettagli del servizio — durata, nome, prezzo. Li incorporo al momento della prenotazione invece di referenziare il documento del servizio. Questo significa che se un barbiere cambia il prezzo di un servizio in seguito, gli appuntamenti esistenti mantengono il prezzo con cui sono stati prenotati. È il comportamento corretto: una prenotazione è uno snapshot di ciò che è stato concordato, non un riferimento live al pricing corrente.
In Clover e Argan, i messaggi referenziano ID di stanza e utente invece di incorporare documenti completi. Si interrogano spesso i messaggi senza aver bisogno dei metadati di stanza o utente. Incorporarli significherebbe recuperare quei metadati ad ogni messaggio anche quando non servono — e in un'app di messaggistica ad alto volume, si accumula.
La decisione riguarda sempre i pattern di accesso. In caso di dubbio, chiedi: avrò mai bisogno di questo dato senza l'altro?
Gli indici sono il design
Gli indici vengono trattati solitamente come ottimizzazione delle performance — qualcosa che aggiungi dopo quando le query diventano lente. Io li penso diversamente: gli indici sono lo schema. Se non sai su quali campi interrogherai e filtrerai, non conosci il tuo design.
In pratica, definisco gli indici quando scrivo il setup della collezione, non dopo. Nel modulo db.ts che uso per inizializzare le connessioni MongoDB, la creazione degli indici parte allo startup. Se mi rendo conto di aggiungere un nuovo indice a una collezione che è in produzione da mesi, è un segnale che non ho capito pienamente i pattern di accesso quando ho progettato lo schema — non solo un fix di performance.
Ogni query che gira senza un indice fa una full collection scan. È invisibile a basso volume e catastrofico in scala. Pensare agli indici presto è economico. Aggiungerli retroattivamente a una collezione in produzione con milioni di documenti, mentre l'applicazione gira, non lo è.
Cosa mi ha insegnato l'IoT
Le applicazioni IoT lavorano con letture di sensori: temperatura, umidità, pressione, con timestamp e continue. Questo tipo di dati ha una struttura forzata dalla sua natura, non dalle scelte di schema.
I dati dei sensori sono append-only. Non aggiorni mai una lettura passata — è avvenuta, è immutabile. Le query sono quasi sempre range-based: "tutte le letture di questo sensore tra questi due timestamp." Non c'è un join significativo tra entità. Lo schema si progetta quasi da solo una volta che interiorizzsi il pattern di accesso.
Lavorare con dati time-series ha cambiato come penso al design degli schemi in generale. La maggior parte dei dati ha una struttura naturale se guardi come si muovono effettivamente nel sistema. Gli appuntamenti vengono creati e poi solo letti. I messaggi vengono aggiunti e interrogati per recency. Le fatture vengono scritte una volta e occasionalmente aggiornate. Capire il rapporto scrittura/lettura/aggiornamento per ogni entità è importante quanto capire le relazioni tra esse.
Il costo nascosto dello schemaless
MongoDB viene spesso descritto come schemaless, il che crea l'impressione che si possa capire la struttura in seguito. È una trappola.
Ciò che "schemaless" significa davvero è che il database non applicherà la struttura — deve farlo la tua applicazione. All'inizio di un progetto sembra libertà. Un anno in produzione, con documenti che hanno accumulato inconsistenze attraverso le versioni del codice, sembra debito. Nomi di campi che sono cambiati. Campi opzionali diventati obbligatori. Strutture annidate che sono state refactorate ma non migrate.
Eseguo migrazioni su MongoDB. Non con un framework — solo script espliciti che versiono e applico come parte dei deployment. Sono meno formalizzati delle migrazioni SQL ma la disciplina è la stessa: ogni cambiamento strutturale ai dati è deliberato, tracciato e applicato in sequenza.
Schemaless significa che lo schema vive nel tuo codice. Va bene, purché lo tratti seriamente.
Il design dello schema è una serie di previsioni su come crescerà la tua applicazione. Alcune saranno sbagliate. L'obiettivo non è azzeccarle tutte in anticipo — è prendere le decisioni con abbastanza consapevolezza che quando sbagli, sai perché, e sai cosa cambiare.
Parti dalle query. Progetta per le letture. Conosci i tuoi indici. Il resto viene da sé.