modulo 17 di 24 / Ore 41-60
Moduli 17-24 / Checkpoint 60 ore
Adapter verso database leggero o file JSON
Come scrivere un adapter Node.js che legge e scrive un file JSON con fs, rispettando lo stesso contratto degli altri adapter nel Registro del laboratorio scolastico.
Adapter verso database leggero o file JSON
Questa lezione appartiene al Modulo 17 — Variante database o API.
Il lavoro continua il progetto guida, il Registro del laboratorio scolastico, aggiungendo una variante infrastrutturale basata su file JSON letto con Node.js — senza modificare dominio o casi d’uso.
Obiettivo della lezione
Scrivi un adapter che legge e scrive un file db.json usando fs.readFileSync e fs.writeFileSync di Node.js. Il contratto esposto deve essere identico a quello degli altri adapter del Registro.
Alla fine devi avere: un file infrastructure/archivioStrumentiFile.js funzionante, con lo stesso contratto di archivioStrumentiLocalStorage.js.
Il contratto da rispettare
Tutti gli adapter del Registro rispettano lo stesso contratto:
caricaTutti() // → Strumento[] (o Promise<Strumento[]>)
salva(strumenti) // → void (o Promise<void>)
trovaPerId(id) // → Strumento | null
L’adapter per file JSON usa operazioni sincrone (readFileSync, writeFileSync), quindi le funzioni possono restituire valori diretti senza async/await. Questo semplifica l’implementazione rispetto all’adapter fetch, ma il contratto rimane lo stesso.
Come funziona fs con un file JSON
Node.js include il modulo fs nella libreria standard. Le due operazioni fondamentali per leggere e scrivere JSON sono:
import { readFileSync, writeFileSync } from "fs";
// leggere
const testo = readFileSync("db.json", "utf-8");
const dati = JSON.parse(testo);
// scrivere
writeFileSync("db.json", JSON.stringify(dati, null, 2), "utf-8");
null, 2 nella JSON.stringify serve a indentare il file con due spazi — utile per ispezionarlo a mano durante il debug.
Struttura del file db.json
Il file JSON deve avere la stessa struttura logica dei dati in localStorage, ma come file su disco:
{
"strumenti": [
{ "id": "s1", "name": "Multimetro", "category": "misura", "status": "disponibile" },
{ "id": "s2", "name": "Oscilloscopio", "category": "misura", "status": "in-prestito" }
],
"prestiti": [
{ "id": "p1", "strumentoId": "s2", "dataApertura": "2025-10-01", "dataCiusura": null }
]
}
I nomi dei campi sono in inglese per separare il formato di persistenza dal modello di dominio (dove si usa nome, categoria, stato).
Nel Registro del laboratorio
Il caso d’uso CercaStrumenti chiama archivio.caricaTutti() e riceve un array di oggetti Strumento. Non sa se quei dati vengono da localStorage, da un file JSON o da un’API. Cambiare l’adapter in main.js è sufficiente:
// main.js — per usare l'adapter file JSON
import * as archivio from "./infrastructure/archivioStrumentiFile.js";
Procedura guidata
- Crea il file
db.jsoncon la struttura iniziale (strumenti vuoto, prestiti vuoto). - Crea
src/infrastructure/archivioStrumentiFile.js. - Importa
readFileSyncewriteFileSyncda"fs". - Scrivi una funzione privata
leggiDB()che legge e fa il parse del file. - Scrivi una funzione privata
scriviDB(dati)che serializza e scrive il file. - Implementa le tre funzioni del contratto usando
leggiDBescriviDB. - Sostituisci l’import in
main.jse verifica che i casi d’uso funzionino invariati.
Attività in classe
Compila questa scheda dopo aver scritto l’adapter:
## Adapter file JSON
Funzione leggiDB — cosa fa:
Funzione scriviDB — cosa fa:
Funzione caricaTutti — implementazione:
Funzione trovaPerId — implementazione:
Funzione salva — implementazione:
Contratto rispettato rispetto a archivioStrumentiLocalStorage: sì / no
Casi d'uso modificati: sì / no — se sì, perché:
Errori gestiti: quali scenari?
Spiegazione guidata per studiare
Un adapter verso file JSON è la variante più trasparente da studiare perché il file su disco è visibile e ispezionabile. Puoi aprire db.json con un editor dopo ogni operazione e verificare esattamente cosa è stato scritto.
Il punto fondamentale è che il contratto non cambia. Il caso d’uso RegistraStrumento chiama archivio.salva(strumenti) e non sa se quella funzione usa localStorage.setItem, writeFileSync o fetch. È il principio di sostituzione applicato all’infrastruttura.
L’unica differenza tecnica rilevante è che fs è sincrono e disponibile solo in Node.js. Se il progetto deve girare nel browser, questo adapter non funziona — ma puoi tenerlo come adapter di test o di script CLI separato. Per il browser, localStorage rimane la scelta principale.
Passo per passo nel progetto
- Crea
db.jsonnella root del progetto:
{
"strumenti": [],
"prestiti": []
}
- Crea
src/infrastructure/archivioStrumentiFile.js:
// infrastructure/archivioStrumentiFile.js
import { readFileSync, writeFileSync } from "fs";
const PERCORSO = "db.json";
function leggiDB() {
try {
const testo = readFileSync(PERCORSO, "utf-8");
return JSON.parse(testo);
} catch {
return { strumenti: [], prestiti: [] };
}
}
function scriviDB(dati) {
writeFileSync(PERCORSO, JSON.stringify(dati, null, 2), "utf-8");
}
function dtoToStrumento(dto) {
return {
id: dto.id,
nome: dto.name,
categoria: dto.category,
stato: dto.status
};
}
function strumentoToDTO(strumento) {
return {
id: strumento.id,
name: strumento.nome,
category: strumento.categoria,
status: strumento.stato
};
}
export function caricaTutti() {
const db = leggiDB();
return db.strumenti.map(dtoToStrumento);
}
export function trovaPerId(id) {
const db = leggiDB();
const dto = db.strumenti.find(s => s.id === id);
return dto ? dtoToStrumento(dto) : null;
}
export function salva(strumenti) {
const db = leggiDB();
db.strumenti = strumenti.map(strumentoToDTO);
scriviDB(db);
}
- Verifica manualmente: esegui uno script Node.js che chiama
salvacon uno strumento e poicaricaTutti, e controlla il filedb.jsondopo l’esecuzione.
// script-test.mjs
import * as archivio from "./src/infrastructure/archivioStrumentiFile.js";
archivio.salva([
{ id: "s1", nome: "Multimetro", categoria: "misura", stato: "disponibile" }
]);
const tutti = archivio.caricaTutti();
console.log(tutti); // deve stampare l'array con il Multimetro
- Esegui con
node script-test.mjse apridb.jsonper ispezionare il risultato.
Esempio JS — gestione del file corrotto
Un caso reale: cosa succede se db.json è vuoto o contiene JSON non valido?
function leggiDB() {
try {
const testo = readFileSync(PERCORSO, "utf-8");
return JSON.parse(testo);
} catch (errore) {
// file non esiste o JSON non valido → partiamo vuoti
console.warn("db.json non leggibile, si parte con archivio vuoto:", errore.message);
return { strumenti: [], prestiti: [] };
}
}
Il catch non nasconde l’errore (viene loggato con console.warn) ma permette al sistema di continuare. Questo è il comportamento corretto per un registro scolastico: meglio partire vuoti che bloccare tutto.
Prova di comprensione
Rispondi per iscritto:
- Perché
leggiDBescriviDBsono funzioni private e non esportate? - Cosa succede se il file
db.jsonviene cancellato mentre l’applicazione è in uso? - Perché i nomi dei campi nel DTO (
name,status) sono diversi da quelli del dominio (nome,stato)? - Come testeresti
trovaPerIdsenza usare il file su disco?
Errori da evitare
- usare direttamente
readFileSyncnei casi d’uso invece di delegare all’adapter; - dimenticare il
try/catchattorno alreadFileSync— il file potrebbe non esistere alla prima esecuzione; - salvare oggetti di dominio direttamente nel JSON senza mappatura — accoppia il formato di persistenza al modello;
- chiamare
scriviDBper ogni operazione di lettura — si scrive solo quando i dati cambiano.
Prodotto da consegnare
Il file src/infrastructure/archivioStrumentiFile.js funzionante, più un piccolo script di verifica che mostra caricaTutti e salva in azione con Node.js. Come controllo: apri db.json dopo l’esecuzione e verifica che i dati siano scritti correttamente.
Checklist di chiusura
- le tre funzioni del contratto sono implementate e esportate;
- le funzioni di mapping separano il formato di persistenza dal modello di dominio;
leggiDBgestisce il caso di file mancante o corrotto;- i casi d’uso non sono stati modificati;
- ho verificato il contenuto di
db.jsondopo almeno unsalva.
Risultato atteso
Ho creato archivioStrumentiFile.js con fs.readFileSync e writeFileSync.
Il contratto è identico agli altri adapter: caricaTutti, salva, trovaPerId.
Ho verificato che i casi d'uso non cambiano — solo l'import in main.js.
Ho aperto db.json dopo il salvataggio e ho visto i dati scritti correttamente.
Il prossimo passo: aggiungere la gestione dei prestiti nell'adapter file.