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.

25 minArchitettura del software

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:

  1. strumento.stato === "in_prestito" — lo strumento sa che è in prestito
  2. archivioPrestiti.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

  1. Apri registraPrestito e chiudiPrestito nel tuo progetto.
  2. Verifica che entrambi aggiornino sia il prestito sia lo strumento.
  3. Aggiungi un test che verifica la coerenza dopo un’operazione: lo strumento ha lo stato corretto?
  4. 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

  1. Cerca tutte le righe del progetto che scrivono stato: "in_prestito" o stato: "disponibile".
  2. Ogni scrittura passa dal caso d’uso corretto? O viene fatta direttamente da qualche altra parte?
  3. Aggiungi verificaCoerenzaStati come funzione di utilità per i test.
  4. 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.stato direttamente 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.stato completamente senza considerare l’impatto sulle query di lista.

Prodotto da consegnare

Può essere:

  • una funzione verificaCoerenzaStati aggiunta al progetto;
  • un test che verifica la coerenza dopo registraPrestito e chiudiPrestito;
  • 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 registraPrestito e chiudiPrestito mantengono 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.