modulo 22 di 24 / Ore 41-60

Moduli 17-24 / Checkpoint 60 ore

Estrarre mapping e adapter dove servono

Come identificare dove estrarre funzioni di mapping (domain→DTO, DTO→domain) e adapter (localStorage, API) nel Registro, con esempi before/after e criteri per decidere quando farlo.

30 min Architettura del software

Estrarre mapping e adapter dove servono

Questa lezione appartiene al Modulo 22 — Refactoring avanzato e varianti.

Obiettivo della lezione

Capire quando e dove estrarre le funzioni di mapping e gli adapter nel Registro del laboratorio scolastico. Alla fine avrai un adapter di localStorage separato dal dominio e una funzione di mapping esplicita tra oggetti di dominio e formato salvato.

Perché serve nel percorso

Senza mapping esplicito, il formato dei dati salvati in localStorage entra nel dominio: le funzioni di caso d’uso iniziano a dipendere dalla struttura JSON, e cambiare come si salvano i dati significa toccare anche le regole. L’adapter isola questo confine.

Nel Registro del laboratorio

Lo strumento ha due rappresentazioni diverse nel progetto:

  • Oggetto di dominio (quello che usa registraPrestito, cercaStrumenti, le regole):
// src/domain/strumento.js
{ id: "str-001", nome: "Oscilloscopio", categoria: "Misura", stato: "disponibile" }
  • DTO salvato in localStorage (quello che scrive e legge l’adapter):
// formato JSON in localStorage["registro_strumenti"]
{ id: "str-001", name: "Oscilloscopio", category: "Misura", status: "disponibile" }

La differenza sembra piccola (nome/name, categoria/category, stato/status), ma senza mapping esplicito i casi d’uso finiscono per usare le chiavi inglesi — oppure localStorage finisce per contenere chiavi italiane. Entrambe le scelte creano accoppiamento nascosto.

Procedura guidata

  1. Identifica in src/infrastructure/ se esiste già un file archivioStrumentiLocalStorage.js.
  2. Cerca se nel file ci sono conversioni dirette tra campo italiano e campo inglese dentro le funzioni salva o trova.
  3. Estrai le conversioni in due funzioni separate.
  4. Sposta le funzioni nel file di mapping.
  5. Aggiorna l’adapter per usarle.
  6. Verifica che il caso d’uso non cambi.

Attività in classe

Analizza questo codice — è il “prima”:

// src/infrastructure/archivioStrumentiLocalStorage.js — PRIMA
function salvaStrumento(strumento) {
  const raw = JSON.parse(localStorage.getItem("registro_strumenti") || "[]");
  raw.push({
    id: strumento.id,
    name: strumento.nome,        // conversione nascosta
    category: strumento.categoria,
    status: strumento.stato
  });
  localStorage.setItem("registro_strumenti", JSON.stringify(raw));
}

function trovaTutti() {
  const raw = JSON.parse(localStorage.getItem("registro_strumenti") || "[]");
  return raw.map(item => ({
    id: item.id,
    nome: item.name,             // conversione nascosta
    categoria: item.category,
    stato: item.status
  }));
}

Il problema: la conversione è duplicata in ogni funzione dell’adapter, e se aggiungi un campo nuovo lo devi cambiare in più punti.

Estrai il mapping in un file separato — questo è il “dopo”:

// src/infrastructure/mapping/strumentoMapping.js — DOPO
export function strumentoToDTO(strumento) {
  return {
    id: strumento.id,
    name: strumento.nome,
    category: strumento.categoria,
    status: strumento.stato
  };
}

export function dtoToStrumento(dto) {
  return {
    id: dto.id,
    nome: dto.name,
    categoria: dto.category,
    stato: dto.status
  };
}
// src/infrastructure/archivioStrumentiLocalStorage.js — DOPO
import { strumentoToDTO, dtoToStrumento } from "./mapping/strumentoMapping.js";

function salvaStrumento(strumento) {
  const raw = JSON.parse(localStorage.getItem("registro_strumenti") || "[]");
  raw.push(strumentoToDTO(strumento));
  localStorage.setItem("registro_strumenti", JSON.stringify(raw));
}

function trovaTutti() {
  const raw = JSON.parse(localStorage.getItem("registro_strumenti") || "[]");
  return raw.map(dtoToStrumento);
}

Compila la scheda:

## Mapping estratto

File di mapping creato:

Campi convertiti da dominio a DTO:

Campi convertiti da DTO a dominio:

Funzioni dell'adapter aggiornate:

Verifica eseguita:

Spiegazione guidata per studiare

Il mapping risolve un problema di accoppiamento: senza di esso, il formato di localStorage deve coincidere esattamente con il modello di dominio. Questo va bene finché sono la stessa cosa — ma appena divergono (perché cambi backend, aggiungi un campo, rinomini qualcosa), devi toccare il dominio per cambiare come si salvano i dati.

Con il mapping estratto, puoi cambiare strumentoToDTO senza toccare Strumento, e puoi cambiare Strumento senza toccare il formato JSON salvato.

Lo stesso schema vale per i prestiti:

// src/infrastructure/mapping/prestitoMapping.js
export function prestitoToDTO(prestito) {
  return {
    id: prestito.id,
    tool_id: prestito.strumentoId,
    borrower: prestito.studenteId,
    opened_at: prestito.apertura.toISOString(),
    closed_at: prestito.chiusura ? prestito.chiusura.toISOString() : null
  };
}

export function dtoToPrestito(dto) {
  return {
    id: dto.id,
    strumentoId: dto.tool_id,
    studenteId: dto.borrower,
    apertura: new Date(dto.opened_at),
    chiusura: dto.closed_at ? new Date(dto.closed_at) : null
  };
}

Nota: il formato ISO 8601 per le date è una scelta dell’infrastruttura. Il dominio usa oggetti Date. Questo confine è esplicito e documentato nel mapping.

Passo per passo nel progetto

  1. Crea la cartella src/infrastructure/mapping/ se non esiste.
  2. Crea strumentoMapping.js con strumentoToDTO e dtoToStrumento.
  3. Crea prestitoMapping.js con prestitoToDTO e dtoToPrestito.
  4. Apri archivioStrumentiLocalStorage.js e sostituisci le conversioni inline con le funzioni di mapping.
  5. Apri archivioPrestitiLocalStorage.js e fai lo stesso.
  6. Esegui i test esistenti: devono passare senza modifiche ai casi d’uso.
  7. Aggiungi un test sul mapping stesso:
// test/mapping/strumentoMapping.test.js
import { strumentoToDTO, dtoToStrumento } from "../../src/infrastructure/mapping/strumentoMapping.js";

test("strumentoToDTO converte le chiavi da italiano a inglese", () => {
  const strumento = { id: "s1", nome: "Trapano", categoria: "Utensile", stato: "disponibile" };
  const dto = strumentoToDTO(strumento);
  expect(dto.name).toBe("Trapano");
  expect(dto.category).toBe("Utensile");
  expect(dto.status).toBe("disponibile");
  expect(dto.nome).toBeUndefined(); // nessuna chiave italiana nel DTO
});

test("dtoToStrumento è l'inverso di strumentoToDTO", () => {
  const originale = { id: "s1", nome: "Trapano", categoria: "Utensile", stato: "disponibile" };
  const dto = strumentoToDTO(originale);
  const ricostruito = dtoToStrumento(dto);
  expect(ricostruito).toEqual(originale);
});

Prova di comprensione

Rispondi per iscritto:

  • Perché estrarre il mapping in un file separato invece di tenerlo dentro l’adapter?
  • Cosa succede se aggiungi il campo seriale a Strumento — in quanti posti devi intervenire con il mapping estratto?
  • Il dominio deve conoscere il file strumentoMapping.js? Perché?
  • Come verifichi che il mapping non ha perso informazioni?

Errori da evitare

  • mettere il mapping direttamente nel caso d’uso (registraPrestito non deve sapere come si chiama il campo in localStorage);
  • usare le chiavi del DTO come nomi nel dominio per evitare il mapping (vince il dominio, non il formato di salvataggio);
  • dimenticare di gestire i valori null nel mapping (ad esempio chiusura: null nei prestiti aperti);
  • non testare il mapping: è logica, non configurazione, e può sbagliare.

Prodotto da consegnare

Due file di mapping (strumentoMapping.js e prestitoMapping.js) con le funzioni di conversione, gli adapter aggiornati per usarli, e almeno un test che verifica il round-trip (domain → DTO → domain).

Checklist di chiusura

  • strumentoToDTO e dtoToStrumento sono in un file separato dall’adapter
  • prestitoToDTO e dtoToPrestito esistono e gestiscono le date
  • nessun adapter contiene conversioni di campo inline
  • nessun caso d’uso importa funzioni di mapping
  • i test esistenti passano senza modifiche
  • il README segnala l’esistenza della cartella mapping/

Risultato atteso

Ho estratto strumentoMapping.js e prestitoMapping.js in src/infrastructure/mapping/.
L'adapter di localStorage usa queste funzioni per convertire tra oggetti di dominio e JSON.
I casi d'uso non sono stati modificati.
Il test del round-trip dimostra che il mapping non perde dati.
Il prossimo passo sarebbe aggiungere gestione degli errori per DTO malformati.