Impara un metodo pratico per trasformare user story, entità e workflow in uno schema di database chiaro, e come il ragionamento AI può aiutare a trovare gap e regole.

Uno schema di database è il progetto di come la tua app ricorderà le cose. In termini pratici, è:
Quando lo schema rispecchia il lavoro reale, riflette ciò che le persone fanno davvero—creare, revisionare, approvare, programmare, assegnare, cancellare—invece di quello che sembra ordinato sulla lavagna.
Le user story e i criteri di accettazione descrivono bisogni reali in linguaggio semplice: chi fa cosa e cosa significa “fatto”. Se le usi come fonte, lo schema è meno soggetto a perdere dettagli chiave (tipo “dobbiamo tracciare chi ha approvato il rimborso” o “una prenotazione può essere riprogrammata più volte”).
Partire dalle story ti mantiene onesto sullo scope. Se non è nelle story (o nel workflow), trattalo come opzionale invece di costruire di nascosto un modello complicato “nel caso serva”.
L’AI può aiutarti ad andare più veloce facendo cose come:
L’AI non può invece affidabilmente:
Considera l’AI un forte assistente, non il decisore finale.
Se vuoi trasformare quell’assistente in momentum, una piattaforma vibe-coding come Koder.ai può aiutarti ad andare dalle decisioni sullo schema a un’app funzionante (React + Go + PostgreSQL) più rapidamente—mentre rimani comunque padrone del modello, dei vincoli e delle migrazioni.
La progettazione dello schema è un loop: bozza → test contro le story → trova i dati mancanti → rifinisci. L’obiettivo non è un output perfetto la prima volta; è un modello che puoi ricondurre a ogni user story e dire con sicurezza: “Sì, possiamo memorizzare tutto ciò che questo workflow richiede—e possiamo spiegare perché ogni tabella esiste.”
Prima di trasformare i requisiti in tabelle, chiarisci cosa stai modellando. Un buon schema raramente parte da zero—parte da lavori concreti che le persone fanno e dalla prova di cui avrai bisogno dopo (schermate, output e casi limite).
Le user story sono il titolo, ma non bastano da sole. Raccogli:
Se usi l’AI, questi input tengono il modello ancorato. L’AI può proporre entità e campi velocemente, ma ha bisogno di artefatti concreti per evitare di inventare strutture che non corrispondono al prodotto.
I criteri di accettazione spesso contengono le regole più importanti per il database, anche quando non menzionano i dati esplicitamente. Cerca frasi come:
Story vaghe (“As a user, I can manage projects”) nascondono più entità e workflow. Un altro gap frequente sono i casi limite mancanti come cancellazioni, retry, rimborsi parziali o riassegnazioni.
Prima di pensare a tabelle o diagrammi, leggi le user story e evidenzia i sostantivi. Nei requisiti, i sostantivi spesso puntano alle “cose” che il sistema deve ricordare—queste diventano spesso entità nello schema.
Un modello mentale rapido: i sostantivi diventano entità, mentre i verbi diventano azioni o workflow. Se una story dice “A manager assigns a technician to a job,” le entità probabili sono manager, technician e job—e “assigns” indica una relazione da modellare dopo.
Non ogni sostantivo merita una tabella. È una buona candidata quando:
Se un sostantivo compare solo una volta o descrive qualcos’altro (“red button”, “Friday”), potrebbe non essere un’entità.
Un errore comune è trasformare ogni dettaglio in una tabella. Usa questa regola pratica:
Due esempi classici:
L’AI può accelerare la scoperta di entità analizzando le story e restituendo una bozza di sostantivi raggruppati per tema (persone, elementi di lavoro, documenti, luoghi). Un prompt utile è: “Extract nouns that represent data we must store, and group duplicates/synonyms.”
Tratta l’output come un punto di partenza, non come la risposta finale. Fai follow-up come:
L’obiettivo dello Step 1 è una lista corta e pulita di entità che puoi difendere puntando alle story reali.
Una volta nominate le entità (es.: Order, Customer, Ticket), il passo successivo è catturare i dettagli che ti serviranno dopo. In un database, quei dettagli sono i campi (o attributi)—i promemoria che il sistema non può permettersi di dimenticare.
Parti dalla user story, poi leggi i criteri di accettazione come una checklist di cosa deve essere salvato.
Se un requisito dice “Users can filter orders by delivery date”, allora delivery_date non è opzionale—deve esistere come campo (o essere derivabile in modo affidabile da altri dati). Se dice “Show who approved the request and when”, probabilmente ti servono approved_by e approved_at.
Un test pratico: Qualcuno avrà bisogno di questo per mostrare, cercare, ordinare, auditare o calcolare qualcosa? Se sì, probabilmente è un campo.
Molte story usano parole come “status”, “type” o “priority”. Trattale come vocabolari controllati—un insieme limitato di valori ammessi.
Se l’elenco è piccolo e stabile, una semplice enum è sufficiente. Se può crescere, ha etichette o richiede permessi (es.: categorie gestite dagli admin), usa una tabella lookup (es.: status_codes) e memorizza un riferimento.
Così le story si trasformano in campi affidabili—ricercabili, reportabili e difficili da inserire male.
Dopo aver elencato le entità (User, Order, Invoice, Comment, ecc.) e abbozzato i loro campi, il passo successivo è collegarle. Le relazioni sono lo strato “come queste cose interagiscono” implicito nelle story.
One-to-one (1:1) significa “una cosa ha esattamente un’altra cosa”.
User ↔ Profile (spesso puoi unirle a meno che non ci sia un motivo per separarle).One-to-many (1:N) significa “una cosa può averne molte”. È la più comune.
User → Order (metti user_id su Order).Many-to-many (M:N) significa “molte cose possono relazionarsi a molte altre”. Serve una tabella in più.
I database non gestiscono bene “una lista di product ID” dentro Order senza creare problemi dopo (ricerche, aggiornamenti, report). Crea invece una join table che rappresenti la relazione stessa.
Esempio:
OrderProductOrderItem (tabella di join)OrderItem tipicamente include:
order_idproduct_idquantity, unit_price, discountNota come i dettagli della story (“quantity”) spesso appartengono alla relazione, non a una singola entità.
Le story dicono anche se una connessione è obbligatoria o talvolta assente.
Order ha bisogno di user_id (non permettere vuoti).phone può essere vuoto.shipping_address_id potrebbe essere vuoto per ordini digitali.Un controllo rapido: se la story implica che non puoi creare il record senza il collegamento, trattalo come obbligatorio. Se la story dice “can”, “may” o dà eccezioni, trattalo come opzionale.
Quando leggi una story, riscrivila come un semplice accoppiamento:
User 1:N CommentComment N:1 UserFallo per ogni interazione nelle story. Alla fine avrai un modello connesso che corrisponde a come il lavoro avviene davvero—prima ancora di aprire uno strumento ER.
Le user story dicono cosa vogliono le persone. I workflow mostrano come il lavoro si muove, passo dopo passo. Tradurre un workflow in dati è uno dei modi più rapidi per scoprire problemi del tipo “ci siamo dimenticati di salvare questo”—prima di costruire.
Scrivi il workflow come sequenza di azioni e cambi di stato. Per esempio:
Quelle parole in grassetto diventano spesso un campo status (o una piccola tabella “state”), con valori ammessi chiari.
Camminando ogni passo chiediti: “Di cosa avremmo bisogno per provarlo più tardi?” I workflow rivelano spesso campi come:
submitted_at, approved_at, completed_atcreated_by, assigned_to, approved_byrejection_reason, approval_notesequence per processi multi-stepSe il processo include attese, escalation o passaggi di mano, di solito ti serve almeno un timestamp e un campo “chi ce l’ha adesso”.
Alcuni passaggi non sono solo campi—sono strutture di dati separate:
Dai all’AI sia: (1) le user story e i criteri, sia (2) i passi del workflow. Chiedile di elencare ogni passo e identificare i dati richiesti per ciascuno (stato, attore, timestamp, output), poi evidenzia qualsiasi requisito che non sia supportato dai campi/tabelle correnti.
In piattaforme come Koder.ai, questo “gap check” è pratico perché puoi iterare velocemente: aggiusti le ipotesi di schema, rigeneri lo scaffolding e continui senza una lunga deviazione manuale.
Quando trasformi le story in tabelle, non stai solo elencando campi—decidi anche come i dati restano identificabili e coerenti nel tempo.
Una primary key identifica univocamente una riga—pensa a una carta d’identità permanente della riga.
Perché ogni riga ne ha bisogno: le story implicano aggiornamenti, riferimenti e cronologie. Se una story dice “Support can view an order and issue a refund”, ti serve un modo stabile per puntare all’ordine—anche se il cliente cambia email, l’indirizzo viene modificato o lo status cambia.
In pratica è di solito un id interno (numero o UUID) che non cambia.
Una foreign key è come un puntatore sicuro. Se orders.customer_id riferisce customers.id, il database può far rispettare che ogni ordine appartiene a un cliente reale.
Questo corrisponde a story come “As a user, I can see my invoices.” L’invoice non galleggia: è attaccata a un customer (e spesso a un order o subscription).
Le user story contengono regole di unicità nascoste:
Queste regole prevengono duplicati confusi che emergono mesi dopo come “bug” nei dati.
Gli indici accelerano ricerche come “trova customer per email” o “lista ordini per customer”. Parti con indici che riflettono le query più comuni e le regole di unicità.
Cosa rimandare: indicizzazione pesante per report rari o filtri speculativi. Registra quei bisogni nelle story, valida lo schema, poi ottimizza basandoti su utilizzo reale e query lente osservate.
La normalizzazione ha un obiettivo semplice: prevenire duplicati conflittuali. Se lo stesso fatto può essere salvato in due posti, prima o poi sarà diverso (due ortografie, due prezzi, due indirizzi “correnti”). Uno schema normalizzato memorizza ogni fatto una volta sola e poi lo riferisce.
1) Attento ai gruppi ripetuti
Se vedi pattern come “Phone1, Phone2, Phone3” o “ItemA, ItemB, ItemC”, è segnale di una tabella separata (es.: CustomerPhones, OrderItems). I gruppi ripetuti complicano ricerca, validazione e scalabilità.
2) Non copiare lo stesso nome/dettaglio in più tabelle
Se CustomerName appare in Orders, Invoices e Shipments, hai più fonti di verità. Mantieni i dettagli del cliente in Customers e memorizza solo customer_id altrove.
3) Evita più colonne per la stessa cosa
Colonne come billing_address, shipping_address, home_address vanno bene se sono concetti davvero diversi. Ma se stai modellando “molti indirizzi di tipi diversi”, usa una tabella Addresses con un campo type.
4) Separa lookup da testo libero
Se gli utenti scelgono da un insieme noto (status, category, role), modella coerentemente: enum vincolata o tabella lookup. Questo evita “Pending” vs “pending” vs “PENDING”.
5) Verifica che ogni campo non-ID dipenda dalla cosa giusta
Un controllo mentale: in una tabella, se una colonna descrive qualcosa di diverso dall’entità principale, probabilmente appartiene altrove. Esempio: Orders non dovrebbe memorizzare product_price a meno che non significhi “prezzo al momento dell’ordine” (uno snapshot storico).
A volte duplicare è voluto:
La chiave è che sia intenzionale: documenta quale campo è la fonte di verità e come le copie si aggiornano.
L’AI può segnalare duplicazioni sospette (colonne ripetute, nomi simili, campi status incoerenti) e suggerire split in tabelle. Gli umani scelgono il trade-off—semplicità vs flessibilità vs performance—basandosi su come il prodotto sarà usato.
Una regola utile: salva i fatti che non puoi ricreare in modo affidabile dopo; calcola tutto il resto.
I dati salvati sono la fonte di verità: line item, timestamp, cambi di stato, chi ha fatto cosa. I dati calcolati derivano da quei fatti: totali, contatori, flag come “is overdue” e rollup come “inventory corrente”.
Se due valori possono essere calcolati dagli stessi fatti, preferisci memorizzare i fatti e calcolare il resto per evitare contraddizioni.
I valori derivati cambiano quando cambiano gli input. Se salvi sia gli input sia il risultato derivato, devi mantenerli sincronizzati in ogni workflow e caso limite (modifiche, rimborsi, spedizioni parziali, retrodatazioni). Una sincronizzazione mancata fa sì che il database racconti due storie diverse.
Esempio: salvare order_total e contemporaneamente order_items. Se qualcuno modifica una quantità o applica uno sconto e il totale non viene aggiornato perfettamente, la contabilità vede un numero diverso dal carrello.
I workflow mostrano quando serve la verità storica, non solo la verità corrente. Se gli utenti devono sapere quale valore era al momento, salva uno snapshot.
Per un ordine potresti salvare:
order_total catturato al checkout (snapshot), perché tasse, sconti e regole di prezzo possono cambiare dopoPer l’inventario, il “livello inventario” si calcola spesso dai movimenti (ricevute, vendite, aggiustamenti). Ma se ti serve audit, salva i movimenti e opzionalmente snapshot periodici per velocizzare i report.
Per il tracciamento login, salva last_login_at come fatto (timestamp di evento). “È attivo negli ultimi 30 giorni?” resta un calcolo.
Usiamo una nota app di support ticket. Passeremo da cinque user story a un ER semplice (entità + campi + relazioni), poi lo confronteremo con un workflow.
Dai sostantivi otteniamo entità principali:
Prima (errore comune): Ticket ha assignee_id, ma non abbiamo garantito che solo gli agent possano essere assegnati.
Dopo: l’AI lo segnala e aggiungi una regola pratica: assignee must be a User with role = “agent” (implementabile via validazione applicativa o constraint/policy DB, a seconda dello stack). Questo evita dati “assigned to customer” che rompono i report più avanti.
Uno schema è “finito” solo quando ogni user story può essere soddisfatta con dati che puoi memorizzare e interrogare in modo affidabile. Il passo di validazione più semplice è prendere ogni story e chiedersi: “Possiamo rispondere a questa domanda dal database, per ogni caso?” Se la risposta è “forse”, il modello ha un gap.
Riscrivi ogni user story come una o più domande di test—quelle che ti aspetteresti da un report, una schermata o un’API. Esempi:
Se non riesci a trasformare una story in una domanda chiara, la story è poco chiara. Se puoi, ma non puoi rispondere con lo schema, manca un campo, una relazione, uno status/evento o un vincolo.
Crea un piccolo dataset (5–20 righe per tabella chiave) che includa casi normali e quelli più scomodi (duplicati, valori mancanti, cancellazioni). Poi “recita” le story con quei dati. Vedrai in fretta problemi tipo “non possiamo sapere quale indirizzo è stato usato al momento dell’acquisto” o “non abbiamo dove salvare chi ha approvato la modifica”.
Chiedi all’AI di generare domande di validazione per ogni story (inclusi edge case e scenari di cancellazione) e di elencare i dati necessari per rispondervi. Confronta quella lista con lo schema: ogni mismatch è un’azione concreta, non una sensazione vaga che “qualcosa non va”.
L’AI può velocizzare la modellazione dati, ma aumenta anche il rischio di esporre informazioni sensibili o di fissare assunzioni sbagliate. Usala come assistente molto veloce: utile, ma con regole.
Condividi input realistici ma sanitizzati:
invoice_total: 129.50, status: "paid")Evita qualsiasi cosa che identifichi persone o riveli operazioni confidenziali:
Se ti serve realismo, genera campioni sintetici che rispettino formati e range—mai copiare righe di produzione.
Gli schemi falliscono quando “tutti hanno dato per scontato” qualcosa di diverso. Accanto al modello ER (o nel repo), tieni un breve registro decisionale:
Questo trasforma l’output dell’AI in conoscenza di team invece che in un artefatto isolato.
Lo schema evolverà con nuove story. Proteggilo con:
Se usi una piattaforma come Koder.ai, sfrutta guardrail come snapshot e rollback quando iteri gli schemi, ed esporta il codice sorgente quando ti serve una revisione tradizionale.
Inizia dalle user story e sottolinea i sostantivi che rappresentano cose che il sistema deve ricordare (es.: Ticket, User, Category).
Promuovi un sostantivo a entità quando:
Mantieni una lista breve che tu possa giustificare indicando frasi specifiche delle story.
Usa un test “attributo vs entità":
customer.phone_number).Un indizio rapido: se ti serve “molti di questi”, probabilmente serve un’altra tabella.
Tratta i criteri di accettazione come una checklist di cosa conservare. Se un requisito dice che devi filtrare/ordinare/mostrare/registrare qualcosa, devi memorizzarla (o poterla ricavare in modo affidabile).
Esempi:
approved_by, approved_atdelivery_dateRiscrivi le frasi delle story in frasi di relazione:
customer_id su orders)order_items)Se la relazione stessa ha dati (quantità, prezzo, ruolo), quei dati vanno sulla tabella di join.
Modella M:N con una tabella di join che contiene entrambe le chiavi esterne più i campi specifici della relazione.
Schema tipico:
ordersproductsSegui il workflow passo dopo passo e chiediti: “Cosa dovremo dimostrare in seguito?”
Aggiunte comuni:
submitted_at, closed_atInizia con:
id)orders.customer_id → customers.id)Poi aggiungi indici per le ricerche più comuni (es.: , , ). Rimanda ottimizzazioni speculative fino a quando non vedi pattern reali di query lente.
Esegui un controllo rapido di consistenza:
Phone1/Phone2, sposta in una tabella figlia.Denormalizza dopo solo con una ragione chiara (performance, report, snapshot di audit) e documenta cosa è autorevole.
Conserva i fatti che non puoi ricreare in modo affidabile; calcola tutto il resto.
Buono da conservare:
Buono da calcolare:
Se conservi valori derivati (es.: ), definisci chiaramente come mantenere la sincronizzazione e testa i casi limite (rimborsi, modifiche, spedizioni parziali).
Usa l’AI per bozze, poi verifica contro i tuoi artefatti.
Prompt pratici:
Linee guida:
emailorder_items con order_id, product_id, quantity, unit_priceEvita di salvare “una lista di ID” in una singola colonna: interrogare, aggiornare e far rispettare l’integrità diventa problematico.
created_byassigned_toclosed_byrejection_reasonSe ti serve “chi ha cambiato cosa e quando”, aggiungi una tabella eventi/audit invece di sovrascrivere un singolo campo.
emailcustomer_idstatus + created_atorder_total