modulo 17 di 24 / Ore 41-60
Moduli 17-24 / Checkpoint 60 ore
Adapter verso API mock
Come scrivere un adapter che usa fetch verso json-server, rispettando lo stesso contratto dell'adapter localStorage nel Registro del laboratorio scolastico.
Adapter verso API mock
Questa lezione appartiene al Modulo 17 — Variante database o API.
Il lavoro non è separato dal resto del corso: continua il progetto guida, il Registro del laboratorio scolastico, e aggiunge una variante infrastrutturale concreta senza toccare dominio o casi d’uso.
Obiettivo della lezione
Scrivi un secondo adapter per il Registro che usa fetch verso json-server invece di localStorage. I casi d’uso non devono sapere quale adapter è in uso.
Alla fine devi avere un risultato verificabile: un file infrastructure/archivioStrumentiAPI.js che rispetta lo stesso contratto di archivioStrumentiLocalStorage.js.
Un adapter verso un’API mock
Un adapter è un modulo che adatta un’interfaccia esterna (localStorage, fetch, IndexedDB) al contratto che i casi d’uso si aspettano. Il contratto del Registro è semplice:
// contratto atteso dai casi d'uso
{
caricaTutti() // → Promise<Strumento[]>
salva(strumenti) // → Promise<void>
trovaPerId(id) // → Promise<Strumento | null>
}
L’adapter localStorage implementa questo contratto usando JSON.parse e JSON.stringify. L’adapter API mock implementa lo stesso contratto usando fetch verso json-server.
json-server è un tool che trasforma un file db.json in un’API REST in localhost. Non richiede backend reale.
Differenza rispetto all’adapter localStorage
Aspetto — localStorage — API mock (json-server)
- Dove vivono i dati: browser, sincrono — server locale, asincrono
- Come si legge:
JSON.parse(localStorage.getItem(chiave))—fetch('/strumenti') - Come si scrive:
localStorage.setItem(chiave, JSON.stringify(dati))—fetch('/strumenti/id', { method: 'PUT', body }) - Errori tipici: dato null o corrotto — rete down, status 404 o 500
- Persistenza: rimane nel browser fino a reset — rimane nel file db.json
La differenza cruciale è che tutte le operazioni con fetch sono asincrone. I casi d’uso devono già usare await se vogliono essere compatibili con entrambi gli adapter.
Nel Registro del laboratorio
Per questa lezione lavori sul caso concreto: registrare uno strumento e caricarlo dalla lista. Vuoi poter fare questa sequenza indipendentemente dall’archivio:
RegistraStrumentochiamaarchivio.salva(strumenti)CercaStrumentichiamaarchivio.caricaTutti()- La UI mostra il risultato
Con l’adapter API mock, il json-server deve essere avviato con un file db.json iniziale:
{
"strumenti": [],
"prestiti": []
}
Procedura guidata
- Installa json-server con
npm install --save-dev json-servere avvialo connpx json-server db.json. - Crea
src/infrastructure/archivioStrumentiAPI.js. - Implementa
caricaTutticon una GET a/strumenti. - Implementa
salvacon una sequenza di PUT o con una logica di merge. - Implementa
trovaPerIdcon una GET a/strumenti/:id. - In
main.js, sostituisci l’import dell’adapter localStorage con quello API mock. - Verifica che
RegistraStrumentoeCercaStrumentifunzionino senza modifiche.
Attività in classe
Scrivi l’adapter API mock e confrontalo con quello localStorage su questa scheda:
## Confronto adapter
Adapter localStorage — caricaTutti:
(scrivi l'implementazione)
Adapter API mock — caricaTutti:
(scrivi l'implementazione)
Contratto rispettato: sì / no
Modifiche ai casi d'uso necessarie: sì / no — motivo:
Errori gestiti: quali?
Se lavori in gruppo, un componente scrive l’adapter e un altro verifica che i casi d’uso non abbiano bisogno di modifiche.
Spiegazione guidata per studiare
Un adapter verso un’API mock risolve un problema preciso: i casi d’uso devono poter funzionare sia in browser (con localStorage) sia in un contesto con server locale (con json-server), senza cambiare una riga di logica applicativa.
Il segreto è che il contratto è uguale. Entrambi gli adapter espongono caricaTutti, salva, trovaPerId. I casi d’uso chiamano queste funzioni e non sanno cosa c’è sotto.
La difficoltà principale dell’adapter API mock è la gestione degli errori di rete. Quando fetch restituisce un status 404 o 500, bisogna convertire quell’errore in qualcosa che il caso d’uso capisce: una stringa di errore come "ARCHIVIO_NON_DISPONIBILE", non un oggetto Response crudo.
Passo per passo nel progetto
- Avvia json-server:
npx json-server --watch db.json --port 3001. - Apri
src/infrastructure/archivioStrumentiLocalStorage.jscome riferimento per il contratto. - Crea
src/infrastructure/archivioStrumentiAPI.jscon la stessa firma delle funzioni. - Implementa
caricaTutti:
// infrastructure/archivioStrumentiAPI.js
const BASE_URL = "http://localhost:3001";
export async function caricaTutti() {
const risposta = await fetch(`${BASE_URL}/strumenti`);
if (!risposta.ok) throw new Error("ARCHIVIO_NON_DISPONIBILE");
const dati = await risposta.json();
return dati.map(dtoToStrumento);
}
- Implementa
trovaPerId:
export async function trovaPerId(id) {
const risposta = await fetch(`${BASE_URL}/strumenti/${id}`);
if (risposta.status === 404) return null;
if (!risposta.ok) throw new Error("ARCHIVIO_NON_DISPONIBILE");
const dto = await risposta.json();
return dtoToStrumento(dto);
}
- Implementa
salva. Con json-server, “salva tutto” si traduce in un PUT per ogni strumento modificato. Per semplicità, puoi fare una PUT a/strumenti/:idper ciascuno:
export async function salva(strumenti) {
for (const strumento of strumenti) {
const dto = strumentoToDTO(strumento);
const risposta = await fetch(`${BASE_URL}/strumenti/${strumento.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(dto)
});
if (!risposta.ok) throw new Error("ARCHIVIO_NON_DISPONIBILE");
}
}
- Aggiungi le funzioni di mapping:
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
};
}
- In
main.js, cambia l’import:
// prima
import * as archivio from "./infrastructure/archivioStrumentiLocalStorage.js";
// dopo
import * as archivio from "./infrastructure/archivioStrumentiAPI.js";
I casi d’uso non cambiano. Questa è la prova che il contratto funziona.
Esempio JS completo
// infrastructure/archivioStrumentiAPI.js
const BASE_URL = "http://localhost:3001";
function strumentoToDTO(strumento) {
return {
id: strumento.id,
name: strumento.nome,
category: strumento.categoria,
status: strumento.stato
};
}
function dtoToStrumento(dto) {
return {
id: dto.id,
nome: dto.name,
categoria: dto.category,
stato: dto.status
};
}
export async function caricaTutti() {
const risposta = await fetch(`${BASE_URL}/strumenti`);
if (!risposta.ok) throw new Error("ARCHIVIO_NON_DISPONIBILE");
const dati = await risposta.json();
return dati.map(dtoToStrumento);
}
export async function trovaPerId(id) {
const risposta = await fetch(`${BASE_URL}/strumenti/${id}`);
if (risposta.status === 404) return null;
if (!risposta.ok) throw new Error("ARCHIVIO_NON_DISPONIBILE");
return dtoToStrumento(await risposta.json());
}
export async function salva(strumenti) {
for (const strumento of strumenti) {
const risposta = await fetch(`${BASE_URL}/strumenti/${strumento.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(strumentoToDTO(strumento))
});
if (!risposta.ok) throw new Error("ARCHIVIO_NON_DISPONIBILE");
}
}
Prova di comprensione
Rispondi per iscritto prima di chiudere:
- Se json-server è spento e chiami
caricaTutti, cosa succede? Come deve rispondere il caso d’uso? - Perché
dtoToStrumentoestrumentoToDTOstanno dentro l’adapter e non nel dominio? - Cosa cambieresti nel caso d’uso
RegistraStrumentopassando da localStorage ad API mock? - Come testeresti l’adapter API mock senza avviare json-server?
Errori da evitare
- usare i nomi dei campi dell’API (es.
name,status) direttamente nel dominio invece di mappare; - non gestire gli errori HTTP — un
!risposta.okdeve sempre diventare un’eccezione chiara; - scrivere logica di business dentro
salvaocaricaTutti; - dimenticare che
fetchè asincrona — ogni chiamata deve essere preceduta daawait; - modificare i casi d’uso per adattarli all’adapter invece di rispettare il contratto.
Prodotto da consegnare
Il file src/infrastructure/archivioStrumentiAPI.js completo, con le tre funzioni del contratto implementate. Come verifica: cambia l’import in main.js, avvia json-server, presta uno strumento e ricarica la pagina — il dato deve persistere nel file db.json.
Checklist di chiusura
archivioStrumentiAPI.jsespone le stesse funzioni diarchivioStrumentiLocalStorage.js;- i casi d’uso non sono stati modificati;
- gli errori HTTP vengono convertiti in eccezioni con messaggio leggibile;
- le funzioni di mapping sono dentro l’adapter, non nel dominio;
- ho verificato manualmente almeno una operazione con json-server avviato.
Risultato atteso
Ho creato archivioStrumentiAPI.js.
Il contratto rispettato ha tre funzioni: caricaTutti, salva, trovaPerId.
Ho cambiato solo l'import in main.js — nessun caso d'uso modificato.
La verifica: ho registrato uno strumento, ricaricato la pagina, lo strumento era ancora presente nel db.json.
Il prossimo passo: aggiungere la gestione del prestito nell'adapter API.