modulo 17 di 24 / Ore 41-60

Moduli 17-24 / Checkpoint 60 ore

Dimostrare che dominio e casi d'uso non cambiano

Come dimostrare durante la presentazione che cambiando l'archivio il dominio non cambia: eseguire gli stessi test con localStorage e con archivio in memoria.

30 minArchitettura del software

Dimostrare che dominio e casi d’uso non cambiano

Questa lezione appartiene al Modulo 17 — Variante database o API.

Il lavoro continua il progetto guida, il Registro del laboratorio scolastico. In questa lezione prepari una dimostrazione concreta che il dominio e i casi d’uso sono indipendenti dall’archivio — cambiando solo l’adapter, il comportamento del sistema resta uguale.

Obiettivo della lezione

Eseguire la stessa suite di test con due archivi diversi — localStorage e archivio in memoria — e dimostrare che i risultati sono identici. I casi d’uso non cambiano, non vengono toccati, non sanno quale archivio stanno usando.

Alla fine devi avere: un file di test che usa lo stesso set di test con due adapter diversi, con output identico.

Perché questa dimostrazione è importante

L’architettura a livelli del Registro promette che la persistenza è un dettaglio sostituibile. Questa è la prova concreta di quella promessa. Se cambi l’adapter e i casi d’uso smettono di funzionare, significa che l’architettura ha una falla — i casi d’uso dipendono da qualcosa di specifico dell’adapter.

Durante la presentazione finale, questa dimostrazione vale più di qualsiasi spiegazione verbale: mostra in diretta che il confine tra application e infrastructure funziona davvero.

Il principio di sostituzione

Il principio che si dimostra è questo: se due componenti rispettano lo stesso contratto, sono interscambiabili. Il contratto degli adapter del Registro è:

caricaTutti()    // → Strumento[]
salva(strumenti) // → void
trovaPerId(id)   // → Strumento | null

Qualsiasi modulo che implementa queste tre funzioni può essere usato al posto di un altro. I casi d’uso non distinguono tra i due.

Procedura guidata

  1. Crea un archivio in memoria che rispetta il contratto.
  2. Scrivi cinque test che usano i casi d’uso reali del Registro.
  3. Esegui i test con l’archivio in memoria — tutti devono passare.
  4. Esegui gli stessi test con l’adapter localStorage (se possibile in Node.js, o con un mock).
  5. Mostra che l’output è identico.

Attività in classe

Completa questa scheda dopo la dimostrazione:

## Dimostrazione sostituzione archivio

Test eseguiti: (elenco)
Con archivio in memoria: passati N/N
Con archivio localStorage: passati N/N
Modifiche ai casi d'uso necessarie: sì / no
Cosa prova questa dimostrazione:

Spiegazione guidata per studiare

La differenza tra un’architettura che “funziona” e una che è “corretta” si vede qui. Un progetto che funziona ma dove i casi d’uso importano localStorage direttamente non supera questa dimostrazione — cambiare l’archivio richiede toccare i casi d’uso.

Un progetto corretto supera la dimostrazione perché i casi d’uso ricevono l’archivio come parametro (dependency injection) e non sanno — né devono sapere — cosa c’è dentro.

Passo per passo nel progetto

  1. Crea src/infrastructure/archivioInMemoria.js — un adapter che usa una variabile invece di localStorage:
// infrastructure/archivioInMemoria.js
export function creaArchivioInMemoria(datiIniziali = []) {
  let dati = datiIniziali.map(d => ({ ...d }));

  return {
    caricaTutti() {
      return dati.map(d => ({ ...d }));
    },
    salva(strumenti) {
      dati = strumenti.map(d => ({ ...d }));
    },
    trovaPerId(id) {
      const trovato = dati.find(d => d.id === id);
      return trovato ? { ...trovato } : null;
    }
  };
}
  1. Crea src/test/test-sostituzione-archivio.js con cinque test che usano i casi d’uso reali:
// src/test/test-sostituzione-archivio.js
import { creaArchivioInMemoria } from "../infrastructure/archivioInMemoria.js";
import { registraStrumento } from "../application/registraStrumento.js";
import { apriPrestito } from "../application/apriPrestito.js";
import { chiudiPrestito } from "../application/chiudiPrestito.js";
import { segnalaGuasto } from "../application/segnalaGuasto.js";
import { cercaStrumenti } from "../application/cercaStrumenti.js";

function ok(desc) { console.log("OK:", desc); }
function ko(desc, e) { console.error("FALLITO:", desc, e?.message ?? e); }

// test 1: registra e cerca
try {
  const archivio = creaArchivioInMemoria();
  const archivioP = creaArchivioInMemoria();
  registraStrumento({ nome: "Multimetro", categoria: "misura" }, archivio);
  const trovati = cercaStrumenti({ filtro: "Multi" }, archivio);
  if (trovati.length !== 1) throw new Error(`atteso 1, trovato ${trovati.length}`);
  if (trovati[0].nome !== "Multimetro") throw new Error("nome errato");
  ok("registra e cerca — archivio in memoria");
} catch (e) { ko("registra e cerca", e); }

// test 2: apri prestito su strumento disponibile
try {
  const archivio = creaArchivioInMemoria([
    { id: "str-001", nome: "Oscilloscopio", categoria: "misura", stato: "disponibile" }
  ]);
  const archivioP = creaArchivioInMemoria();
  const prestito = apriPrestito({ strumentoId: "str-001" }, archivio, archivioP);
  if (!prestito.id) throw new Error("prestito senza ID");
  const strumento = archivio.trovaPerId("str-001");
  if (strumento.stato !== "in-prestito") throw new Error("stato non aggiornato");
  ok("apri prestito — archivio in memoria");
} catch (e) { ko("apri prestito", e); }

// test 3: chiudi prestito
try {
  const archivio = creaArchivioInMemoria([
    { id: "str-002", nome: "Saldatore", categoria: "saldatura", stato: "in-prestito" }
  ]);
  const archivioP = creaArchivioInMemoria([
    { id: "pre-001", strumentoId: "str-002", dataApertura: "2025-10-01", dataChiusura: null }
  ]);
  chiudiPrestito({ strumentoId: "str-002" }, archivio, archivioP);
  const strumento = archivio.trovaPerId("str-002");
  if (strumento.stato !== "disponibile") throw new Error("stato non tornato a disponibile");
  ok("chiudi prestito — archivio in memoria");
} catch (e) { ko("chiudi prestito", e); }

// test 4: segnala guasto
try {
  const archivio = creaArchivioInMemoria([
    { id: "str-003", nome: "Trapano", categoria: "perforazione", stato: "disponibile" }
  ]);
  segnalaGuasto({ strumentoId: "str-003" }, archivio);
  const strumento = archivio.trovaPerId("str-003");
  if (strumento.stato !== "guasto") throw new Error("stato non aggiornato a guasto");
  ok("segnala guasto — archivio in memoria");
} catch (e) { ko("segnala guasto", e); }

// test 5: errore — strumento non trovato
try {
  const archivio = creaArchivioInMemoria();
  const archivioP = creaArchivioInMemoria();
  apriPrestito({ strumentoId: "non-esiste" }, archivio, archivioP);
  ko("errore strumento non trovato", new Error("avrebbe dovuto lanciare"));
} catch (e) {
  if (e.message === "STRUMENTO_NON_TROVATO") ok("errore STRUMENTO_NON_TROVATO — archivio in memoria");
  else ko("errore strumento non trovato — tipo sbagliato", e);
}

console.log("\nTutti i test eseguiti con archivio in memoria.");
console.log("I casi d'uso non sono stati modificati.");
  1. Esegui con node src/test/test-sostituzione-archivio.js.
  2. Mostra il risultato: tutti “OK”, nessuna modifica ai casi d’uso.

Esempio JS — come cambiare l’archivio in main.js per la dimostrazione

// main.js — versione con archivio localStorage (produzione)
import * as archivioStrumenti from "./src/infrastructure/archivioStrumentiLocalStorage.js";

// main.js — versione con archivio in memoria (demo di sostituzione)
import { creaArchivioInMemoria } from "./src/infrastructure/archivioInMemoria.js";
const archivioStrumenti = creaArchivioInMemoria();

// I casi d'uso sono identici in entrambi i casi:
import { apriPrestito } from "./src/application/apriPrestito.js";
// apriPrestito non sa quale archivio sta usando.

Prova di comprensione

Rispondi per iscritto:

  • Cosa prova il fatto che i test passano con entrambi gli adapter?
  • Se dovessi aggiungere un terzo adapter (file JSON con Node.js), cosa dovresti scrivere per passare gli stessi test?
  • Cosa succederebbe se apriPrestito usasse localStorage.getItem direttamente invece di riceverlo come parametro?
  • Qual è la differenza tra “i test passano” e “il comportamento è corretto”?

Errori da evitare

  • modificare i casi d’uso per farli funzionare con l’archivio in memoria — se devi farlo, l’architettura è sbagliata;
  • usare localStorage nell’archivio in memoria — non ha senso e richiede il browser;
  • non copiare i dati nell’archivio in memoria — la mutazione condivisa tra test causa bug difficili da trovare.

Prodotto da consegnare

Il file src/test/test-sostituzione-archivio.js con cinque test che passano con l’archivio in memoria, senza modifiche ai casi d’uso. Come verifica per la presentazione: mostra il cambio dell’import in main.js e riesegui i test.

Checklist di chiusura

  • archivioInMemoria.js rispetta il contratto con le tre funzioni;
  • i cinque test passano tutti con l’archivio in memoria;
  • i file dei casi d’uso non sono stati toccati per questa dimostrazione;
  • so cambiare l’import in main.js e rieseguire la demo in meno di 30 secondi;
  • so spiegare verbalmente cosa dimostra questo test.

Risultato atteso

Ho eseguito 5 test con archivio in memoria — tutti OK.
I casi d'uso (apriPrestito, chiudiPrestito, ecc.) non sono stati modificati.
Cambio l'import in main.js da localStorage a in memoria in una riga.
Questa è la prova che l'infrastruttura è un dettaglio sostituibile.