modulo 19 di 24 / Ore 41-60
Moduli 17-24 / Checkpoint 60 ore
Duplicazione dati tra strumenti e prestiti
Riconoscere la duplicazione dello stato tra strumento e prestito nel Registro, capire quando è accettabile e quando crea incoerenza, e come gestirla.
Duplicazione dati tra strumenti e prestiti
Questa lezione appartiene al Modulo 19 — Prestazioni e manutenzione.
Il problema concreto
Nel Registro del laboratorio, lo stesso fatto viene salvato in due posti:
strumento.stato === "in_prestito"— lo strumento sa che è in prestitoarchivioPrestiti.leggiTutti().filter(p => !p.chiusoIl)— i prestiti sanno quali strumenti sono occupati
Questi due dati dovrebbero sempre essere coerenti. Se uno strumento ha stato: "in_prestito" ma non esiste nessun prestito aperto per quell’ID, i dati sono incoerenti. Il sistema mostra uno strumento occupato che in realtà è disponibile — o viceversa.
Questa è denormalizzazione: la stessa informazione è salvata in due posti.
Perché il Registro fa questa scelta
La denormalizzazione nel Registro non è un errore — è una scelta consapevole con vantaggi e rischi.
Vantaggi:
- Sapere se uno strumento è disponibile è immediato: basta controllare
strumento.stato, senza scandire tutti i prestiti. - La lista strumenti con il loro stato è leggibile in un’unica query.
Rischi:
- Se il salvataggio del prestito riesce ma il salvataggio dello strumento fallisce (o viceversa), i dati diventano incoerenti.
- Se si modifica lo stato dello strumento direttamente (senza passare dal caso d’uso), si crea un’incoerenza silenziosa.
Come si crea un’incoerenza
Scenario: registraPrestito salva il prestito ma poi lancia un errore prima di aggiornare lo strumento.
async function registraPrestito({ archivioStrumenti, archivioPrestiti }, comando) {
const strumento = await archivioStrumenti.trovaPerId(comando.strumentoId);
// ...controlli...
const prestito = apriPrestito(strumento.id, new Date().toISOString());
await archivioPrestiti.salva({ ...prestito, assegnatario: comando.assegnatario });
// ← Se il codice si interrompe qui (eccezione, crash, tab chiusa)...
await archivioStrumenti.salva({ ...strumento, stato: "in_prestito" });
// il prestito esiste, ma lo strumento dice ancora "disponibile"
}
Risultato: il prestito è registrato, ma lo strumento appare disponibile. Qualcuno può aprire un secondo prestito sullo stesso strumento.
Quando la duplicazione è accettabile
In un contesto scolastico con pochi strumenti (decine, non migliaia), questa duplicazione è accettabile perché:
- il rischio di incoerenza è basso (operazioni rare, un utente alla volta)
- la semplicità del codice vale più dell’overhead di gestire la consistenza atomica
- non esiste una transazione vera (localStorage non è un database ACID)
Il problema diventa reale solo se:
- più utenti usano il sistema contemporaneamente (concorrenza)
- il numero di strumenti cresce significativamente
- si passa a un database reale dove si può usare una transazione
Come ridurre il rischio di incoerenza
Soluzione 1: la fonte di verità unica
Scegli un’unica fonte autorevole e derivare l’altra al momento:
// La fonte di verità è archivioPrestiti.
// Lo stato dello strumento viene calcolato, non salvato.
function isInPrestito(strumentoId, prestitiAperti) {
return prestitiAperti.some(p => p.strumentoId === strumentoId && !p.chiusoIl);
}
Vantaggio: non c’è duplicazione, quindi non c’è incoerenza. Svantaggio: ogni controllo richiede di caricare tutti i prestiti.
Soluzione 2: mantenere la duplicazione ma rendere esplicito il rischio
Documenta che i due dati devono sempre essere aggiornati insieme, e aggiungi un controllo di coerenza:
// Funzione di verifica — utile in fase di debug o test
function verificaCoerenzaStati(strumenti, prestitiAperti) {
const idInPrestito = new Set(prestitiAperti.map(p => p.strumentoId));
const incoerenti = strumenti.filter(s => {
const haPrestitoAperto = idInPrestito.has(s.id);
const diceDiEssereInPrestito = s.stato === "in_prestito";
return haPrestitoAperto !== diceDiEssereInPrestito;
});
return incoerenti;
}
Questa funzione non corregge l’incoerenza — la rileva. Utile nei test e nelle demo per verificare che il sistema sia coerente dopo ogni operazione.
Dove tenere fermo il confine
Il caso d’uso è l’unico posto autorizzato ad aggiornare entrambi i dati. Né il dominio né la UI devono toccare direttamente strumento.stato senza passare dal caso d’uso.
UI → caso d'uso → [aggiorna prestito] + [aggiorna strumento]
entrambi insieme, o nessuno dei due
Procedura guidata
- Apri
registraPrestitoechiudiPrestitonel tuo progetto. - Verifica che entrambi aggiornino sia il prestito sia lo strumento.
- Aggiungi un test che verifica la coerenza dopo un’operazione: lo strumento ha lo stato corretto?
- Documenta in un commento o nel README che
statoè un dato derivato mantenuto per comodità.
Attività in classe
Lavora su una scheda breve:
## Duplicazione dati tra strumenti e prestiti
Dove nel mio progetto lo stesso fatto è salvato in due posti:
Quale dei due considero la fonte di verità:
Ho trovato un caso in cui i due dati potrebbero diventare incoerenti:
Ho scritto un controllo (funzione, test o scenario manuale):
Decisione: mantengo la duplicazione perché / rimuovo la duplicazione perché:
Spiegazione guidata per studiare
La duplicazione dati tra strumenti e prestiti è un esempio concreto di denormalizzazione: la stessa informazione (questo strumento è in prestito) è salvata in due posti (strumento.stato e archivioPrestiti).
Il punto centrale è: la denormalizzazione è una scelta, non un errore. La scelta va fatta consapevolmente, documentata, e protetta con un vincolo: solo il caso d’uso può aggiornare entrambi i dati insieme.
Il rischio tipico è che qualcuno aggiorni solo un lato — il prestito o lo strumento — e l’altro rimanga obsoleto. Il risultato è un’incoerenza silenziosa: il sistema funziona ma mostra dati sbagliati.
Per un laboratorio scolastico, questa incoerenza è accettabile in un progetto base. Diventa un problema in sistemi più grandi o multi-utente.
Passo per passo nel progetto
- Cerca tutte le righe del progetto che scrivono
stato: "in_prestito"ostato: "disponibile". - Ogni scrittura passa dal caso d’uso corretto? O viene fatta direttamente da qualche altra parte?
- Aggiungi
verificaCoerenzaStaticome funzione di utilità per i test. - Scrivi un commento nel caso d’uso che spiega: “qui aggiorniamo sia il prestito sia lo strumento — devono restare coerenti”.
Esempio da leggere lentamente
// Caso d'uso che mantiene la coerenza tra i due archivi
async function chiudiPrestito({ archivioStrumenti, archivioPrestiti }, { prestitoId }) {
const prestito = await archivioPrestiti.trovaPerId(prestitoId);
if (!prestito) throw new Error("PRESTITO_NON_TROVATO");
if (prestito.chiusoIl) throw new Error("PRESTITO_GIA_CHIUSO");
const prestitoChiuso = { ...prestito, chiusoIl: new Date().toISOString() };
await archivioPrestiti.salva(prestitoChiuso);
// Aggiorna anche lo strumento — i due dati devono restare coerenti
const strumento = await archivioStrumenti.trovaPerId(prestito.strumentoId);
if (strumento) {
await archivioStrumenti.salva({ ...strumento, stato: "disponibile" });
}
}
Nota: se il salvataggio del prestito riesce ma quello dello strumento fallisce, i dati diventano incoerenti. In localStorage non c’è una soluzione perfetta a questo problema — solo la consapevolezza del rischio.
Prova di comprensione
- Nel tuo Registro, dove viene salvato il fatto “questo strumento è in prestito”?
- Se il tab del browser viene chiuso dopo aver salvato il prestito ma prima di aggiornare lo strumento, cosa succede?
- Come potresti rilevare un’incoerenza senza correggerla automaticamente?
Errori da evitare
- aggiornare
strumento.statodirettamente dalla UI, senza passare dal caso d’uso; - aggiornare solo il prestito senza aggiornare lo strumento (o viceversa);
- non documentare che la duplicazione è intenzionale;
- rimuovere
strumento.statocompletamente senza considerare l’impatto sulle query di lista.
Prodotto da consegnare
Può essere:
- una funzione
verificaCoerenzaStatiaggiunta al progetto; - un test che verifica la coerenza dopo
registraPrestitoechiudiPrestito; - una nota nel README che descrive la duplicazione e perché è accettabile nel contesto scolastico.
Checklist di chiusura
- Ho identificato dove lo stesso dato è salvato in due posti
- So quale dei due è la fonte di verità (o ho scelto di non averne una)
- Il caso d’uso aggiorna entrambi i lati della duplicazione
- Ho un modo per rilevare incoerenze (test o funzione di verifica)
- Ho documentato la scelta di mantenere la duplicazione
Domande per la revisione
- Hai trovato altri punti nel progetto dove lo stesso fatto è salvato due volte?
- La tua scelta (tenere la duplicazione o rimuoverla) è documentata?
- Come testeresti che
registraPrestitoechiudiPrestitomantengono la coerenza?
Risultato atteso
Alla fine lo studente deve saper dire:
Nel mio Registro, lo stato "in_prestito" è salvato in [X] e in [Y].
Ho scelto di [mantenere / rimuovere] la duplicazione perché [motivo].
Ho verificato la coerenza con [funzione / test / scenario manuale].
Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.