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.

30 min Architettura del software

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:

  1. RegistraStrumento chiama archivio.salva(strumenti)
  2. CercaStrumenti chiama archivio.caricaTutti()
  3. 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

  1. Installa json-server con npm install --save-dev json-server e avvialo con npx json-server db.json.
  2. Crea src/infrastructure/archivioStrumentiAPI.js.
  3. Implementa caricaTutti con una GET a /strumenti.
  4. Implementa salva con una sequenza di PUT o con una logica di merge.
  5. Implementa trovaPerId con una GET a /strumenti/:id.
  6. In main.js, sostituisci l’import dell’adapter localStorage con quello API mock.
  7. Verifica che RegistraStrumento e CercaStrumenti funzionino 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

  1. Avvia json-server: npx json-server --watch db.json --port 3001.
  2. Apri src/infrastructure/archivioStrumentiLocalStorage.js come riferimento per il contratto.
  3. Crea src/infrastructure/archivioStrumentiAPI.js con la stessa firma delle funzioni.
  4. 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);
}
  1. 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);
}
  1. Implementa salva. Con json-server, “salva tutto” si traduce in un PUT per ogni strumento modificato. Per semplicità, puoi fare una PUT a /strumenti/:id per 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");
  }
}
  1. 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
  };
}
  1. 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é dtoToStrumento e strumentoToDTO stanno dentro l’adapter e non nel dominio?
  • Cosa cambieresti nel caso d’uso RegistraStrumento passando 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.ok deve sempre diventare un’eccezione chiara;
  • scrivere logica di business dentro salva o caricaTutti;
  • dimenticare che fetch è asincrona — ogni chiamata deve essere preceduta da await;
  • 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.js espone le stesse funzioni di archivioStrumentiLocalStorage.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.