modulo 17 di 24 / Ore 41-60
Moduli 17-24 / Checkpoint 60 ore
Definire cosa deve restare invariato
Identificare le parti del Registro che non devono cambiare quando si sostituisce l'infrastruttura: dominio, casi d'uso e contratti tra livelli.
Definire cosa deve restare invariato
Questa lezione appartiene al Modulo 17 — Variante database o API.
Il problema
Vuoi sostituire localStorage con un’API reale. Oppure passi da un file JSON a un database SQLite. Oppure cambi solo la struttura dei dati salvati.
La domanda è: cosa devi riscrivere?
Se il progetto è ben strutturato, la risposta è: solo l’infrastruttura. Dominio e casi d’uso non si toccano. Se invece il progetto ha le responsabilità mescolate, cambiare l’archivio significa riscrivere tutto.
Questa lezione ti aiuta a capire, prima di fare il cambio, cosa deve restare fermo e perché.
Cosa deve restare invariato
Il dominio
Le funzioni del dominio descrivono le regole del laboratorio. Non dipendono da come i dati vengono salvati.
Queste funzioni non devono cambiare quando cambia l’infrastruttura:
// domain/strumento.js — resta invariato qualunque sia l'archivio
function creaStrumento({ id, nome, categoria }) {
if (!id) throw new Error("ID_MANCANTE");
if (!nome || nome.trim().length < 2) throw new Error("NOME_NON_VALIDO");
return { id, nome: nome.trim(), categoria, stato: "disponibile" };
}
function puoEsserePrestato(strumento) {
return strumento.stato === "disponibile";
}
Se stai cambiando l’infrastruttura e ti trovi a modificare creaStrumento o puoEsserePrestato, hai trovato una dipendenza nascosta da correggere prima.
I casi d’uso
I casi d’uso coordinano le operazioni. Ricevono archivi come dipendenze — non li costruiscono internamente.
// application/registraPrestito.js — resta invariato
function creaRegistraPrestito({ archivioStrumenti, archivioPrestiti }) {
return async function registraPrestito(comando) {
const strumento = await archivioStrumenti.trovaPerId(comando.strumentoId);
if (!strumento) throw new Error("STRUMENTO_NON_TROVATO");
if (!puoEsserePrestato(strumento)) throw new Error("STRUMENTO_NON_DISPONIBILE");
const prestito = apriPrestito(strumento.id, new Date().toISOString());
await archivioPrestiti.salva({ ...prestito, assegnatario: comando.assegnatario });
await archivioStrumenti.salva({ ...strumento, stato: "in_prestito" });
};
}
Questo caso d’uso funziona con localStorage, con un’API, con un file JSON o con un archivio in memoria per i test. Non sa quale archivio riceve — parla solo con il contratto.
Il contratto dell’archivio
Il contratto è l’insieme dei metodi che un archivio deve avere. In TypeScript sarebbe un’interfaccia; in JavaScript è semplicemente la forma attesa:
// Contratto atteso da archivioStrumenti — resta invariato
// trovaPerId(id: string) → Strumento | null
// salva(strumento: Strumento) → void
// leggiTutti() → Strumento[]
Finché il tuo nuovo archivio rispetta questo contratto, il caso d’uso non si accorge del cambiamento.
Cosa può cambiare
Solo il livello Infrastructure. L’adapter che parla con localStorage:
// infrastructure/archivioStrumentiLocalStorage.js — QUESTO cambia
function creaArchivioStrumentiLocalStorage() {
const CHIAVE = "registro:strumenti";
function leggiTutti() {
try {
return JSON.parse(localStorage.getItem(CHIAVE) || "[]");
} catch {
return [];
}
}
function trovaPerId(id) {
return leggiTutti().find(s => s.id === id) ?? null;
}
function salva(strumento) {
const lista = leggiTutti();
const indice = lista.findIndex(s => s.id === strumento.id);
if (indice >= 0) lista[indice] = strumento;
else lista.push(strumento);
localStorage.setItem(CHIAVE, JSON.stringify(lista));
}
return { leggiTutti, trovaPerId, salva };
}
Puoi sostituire questo con un adapter che chiama fetch("/api/strumenti") — e il caso d’uso registraPrestito non sa nulla del cambio.
Come verificare che il confine regge
Il test più diretto: esegui il caso d’uso con due archivi diversi e verifica che il comportamento sia identico.
// test: stesso caso d'uso, due archivi — entrambi devono passare
const archivioMemoria = creaArchivioStrumentiInMemoria();
const archivioLS = creaArchivioStrumentiLocalStorage();
const registraConMemoria = creaRegistraPrestito({
archivioStrumenti: archivioMemoria,
archivioPrestiti: creaArchivioPrestiti()
});
const registraConLS = creaRegistraPrestito({
archivioStrumenti: archivioLS,
archivioPrestiti: creaArchivioPrestiti()
});
// Entrambi devono lanciare lo stesso errore con uno strumento inesistente
Se entrambi si comportano allo stesso modo, il dominio è davvero indipendente dall’infrastruttura.
Procedura guidata
- Prima di cambiare archivio, elenca per iscritto: dominio, casi d’uso, contratti — questi non si toccano.
- Identifica il nuovo adapter da scrivere (il file che cambia).
- Verifica che il nuovo adapter rispetti il contratto atteso (stessi metodi, stessa forma dei dati restituiti).
- Esegui i test esistenti con il nuovo adapter — devono passare senza modifiche ai casi d’uso.
- Se un test fallisce, il problema è nell’adapter, non nel dominio.
Attività in classe
Lavora su una scheda breve:
## Definire cosa deve restare invariato
Archivio attuale:
Archivio nuovo (o ipotetico):
Contratto che entrambi devono rispettare (metodi e firme):
Cosa ho verificato che non cambia:
Limite rimasto:
Spiegazione guidata per studiare
Quando cambi l’infrastruttura, il dominio e i casi d’uso devono restare identici. Questo non è un obiettivo astratto: è verificabile eseguendo gli stessi test con un archivio diverso.
Per definire cosa deve restare invariato, il punto centrale è: il caso d’uso non costruisce l’archivio — lo riceve. Questa separazione è ciò che rende possibile sostituire localStorage con un’API senza toccare le regole del laboratorio.
Il rischio tipico è scrivere un caso d’uso che chiama localStorage.getItem direttamente. In quel momento il caso d’uso e l’infrastruttura sono accoppiati — cambiare uno significa cambiare l’altro. La soluzione è spostare quella chiamata in un adapter e passare l’adapter come dipendenza.
Passo per passo nel progetto
- Apri i file dei casi d’uso (
application/). CercanolocalStorage,fetch,JSON.parsedirettamente? Se sì, c’è una dipendenza da estrarre. - Elenca i metodi che il caso d’uso chiama sull’archivio (
trovaPerId,salva,leggiTutti). - Scrivi un adapter alternativo che rispetta quegli stessi metodi.
- Sostituisci l’archivio nel punto di composizione (
main.js) — solo lì. - Verifica che il comportamento del Registro sia identico.
Esempio da leggere lentamente
// main.js — il SOLO punto dove si sceglie l'archivio
import { creaArchivioStrumentiLocalStorage } from "./infrastructure/archivioStrumentiLocalStorage.js";
import { creaArchivioStrumentiAPI } from "./infrastructure/archivioStrumentiAPI.js";
import { creaRegistraPrestito } from "./application/registraPrestito.js";
// Cambia solo questa riga per passare da localStorage a API:
const archivioStrumenti = creaArchivioStrumentiLocalStorage();
// const archivioStrumenti = creaArchivioStrumentiAPI(); ← basta decommentare
const registraPrestito = creaRegistraPrestito({ archivioStrumenti, archivioPrestiti });
Nota: registraPrestito non cambia. creaArchivioStrumentiAPI deve avere gli stessi metodi di creaArchivioStrumentiLocalStorage.
Prova di comprensione
- Elenca tre cose del Registro che non devono cambiare quando cambia l’archivio.
- Cosa significa che un archivio “rispetta il contratto”?
- Se il tuo caso d’uso smette di funzionare dopo aver cambiato archivio, dove cerchi il problema?
Errori da evitare
- chiamare
localStorageofetchdirettamente dentro un caso d’uso; - costruire l’archivio dentro il caso d’uso con
newo chiamate dirette; - modificare il dominio o i casi d’uso quando si cambia solo l’archivio;
- non verificare che il nuovo adapter rispetti il contratto prima di collegarlo.
Prodotto da consegnare
Può essere:
- un secondo adapter scritto (es.
archivioStrumentiInMemoria.js) che rispetta lo stesso contratto; - una nota tecnica che elenca cosa resta invariato e cosa cambia;
- un test che esegue lo stesso caso d’uso con due archivi diversi.
Checklist di chiusura
- Il dominio non importa
localStorage,fetcho dettagli di database - I casi d’uso ricevono l’archivio come dipendenza, non lo costruiscono
- Il nuovo adapter ha gli stessi metodi del precedente
- Il comportamento del Registro è identico con entrambi gli archivi
- Il punto di composizione (
main.js) è l’unico posto dove si sceglie l’archivio
Domande per la revisione
- Hai trovato dipendenze nascoste nei casi d’uso? Dove stavano?
- Il contratto dell’archivio è scritto da qualche parte (commento, tipo, test)?
- Che cosa cambieresti nel progetto per rendere ancora più facile la sostituzione?
Risultato atteso
Alla fine lo studente deve saper dire:
Nel mio Registro, queste parti restano invariate quando cambia l'archivio: [elenco].
Il contratto che l'archivio deve rispettare ha questi metodi: [lista].
Ho verificato che [caso d'uso] funziona con [archivio A] e con [archivio B].
Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.