modulo 17 di 24 / Ore 41-60
Moduli 17-24 / Checkpoint 60 ore
Mapping tra dati esterni e dominio
Scrivere funzioni di mapping che trasformano i dati ricevuti da localStorage, file JSON o API nel modello di dominio del Registro, e viceversa.
Mapping tra dati esterni e dominio
Questa lezione appartiene al Modulo 17 — Variante database o API.
Il problema del formato esterno
Quando i dati vengono salvati in localStorage, in un file JSON o restituiti da un’API, spesso hanno un formato diverso dal modello di dominio. Le differenze più comuni:
- Nomi delle proprietà diversi — l’API usa
name, il dominio usanome - Tipi diversi — l’API restituisce date come stringhe ISO, il dominio potrebbe usare oggetti
Date - Campi extra — l’API restituisce campi che il dominio non usa
- Campi mancanti — un dato salvato con una versione precedente non ha tutti i campi
Il mapping è la funzione che traduce tra i due formati. Sta nell’Infrastructure — non nel dominio, non nel caso d’uso.
Mapping da formato esterno a dominio
Scenario: un’API restituisce strumenti con nomi in inglese. Il dominio usa italiano.
// Dati ricevuti dall'API (formato esterno)
const dtoApiStrumento = {
id: "str-042",
name: "Electric drill", // inglese
category: "Electric", // inglese
status: "available", // inglese
last_updated: "2024-01-15T10:30:00Z" // campo extra non usato dal dominio
};
// Funzione di mapping — sta nell'adapter, non nel dominio
function dtoApiToStrumento(dto) {
const statoMap = {
"available": "disponibile",
"on_loan": "in_prestito",
"damaged": "guasto",
"maintenance": "in_manutenzione"
};
return {
id: dto.id,
nome: dto.name,
categoria: dto.category,
stato: statoMap[dto.status] ?? "disponibile"
// last_updated ignorato — il dominio non lo usa
};
}
Mapping da dominio a formato esterno
Quando si salva, la direzione si inverte:
// Funzione di mapping inversa — da dominio a DTO per localStorage/API
function strumentoToDto(strumento) {
const statoMap = {
"disponibile": "available",
"in_prestito": "on_loan",
"guasto": "damaged",
"in_manutenzione": "maintenance"
};
return {
id: strumento.id,
name: strumento.nome,
category: strumento.categoria,
status: statoMap[strumento.stato] ?? "available"
};
}
Mapping con localStorage (formato interno)
Quando si usa localStorage il formato è spesso quasi identico al dominio — ma non sempre. Se il dominio è cambiato nel tempo, potrebbero esserci dati salvati con la versione vecchia.
// Mapping da localStorage con gestione dati mancanti o vecchi
function dtoLocalStorageToStrumento(dto) {
return {
id: dto.id ?? crypto.randomUUID(), // campo mancante → genera nuovo id
nome: dto.nome ?? dto.name ?? "Senza nome", // vecchio formato con "name"
categoria: dto.categoria ?? "Non classificato",
stato: ["disponibile", "in_prestito", "guasto", "in_manutenzione"].includes(dto.stato)
? dto.stato
: "disponibile" // stato non valido → default disponibile
};
}
Dove stanno le funzioni di mapping
infrastructure/
archivioStrumentiLocalStorage.js ← usa dtoLocalStorageToStrumento internamente
archivioStrumentiAPI.js ← usa dtoApiToStrumento internamente
mapping/
strumentoMapping.js ← (opzionale) funzioni di mapping separate
Le funzioni di mapping non escono dall’adapter. Il caso d’uso non chiama mai dtoApiToStrumento direttamente — lo fa l’adapter internamente.
// Dentro l'adapter API — il caso d'uso non vede mai il DTO
async function trovaPerId(id) {
const risposta = await fetch(`/api/strumenti/${id}`);
if (!risposta.ok) throw new Error("ARCHIVIO_NON_DISPONIBILE");
const dto = await risposta.json();
return dtoApiToStrumento(dto); // ← mapping interno all'adapter
}
Gestire campi mancanti e dati corrotti
Un mapping robusto non lancia eccezioni per dati parziali — usa valori di default sensati:
function dtoToStrumento(dto) {
if (!dto || typeof dto !== "object") return null; // dati completamente corrotti
return {
id: typeof dto.id === "string" && dto.id.length > 0
? dto.id
: null, // null = non usabile, verrà filtrato
nome: typeof dto.nome === "string" && dto.nome.trim().length > 0
? dto.nome.trim()
: "Senza nome",
categoria: typeof dto.categoria === "string"
? dto.categoria
: "Non classificato",
stato: ["disponibile", "in_prestito", "guasto", "in_manutenzione"].includes(dto.stato)
? dto.stato
: "disponibile"
};
}
// Nell'adapter: filtra gli oggetti non validi
function leggiTutti() {
const raw = JSON.parse(localStorage.getItem("registro:strumenti") || "[]");
return raw
.map(dtoToStrumento)
.filter(s => s !== null && s.id !== null);
}
Procedura guidata
- Identifica il formato esterno dei tuoi dati (localStorage, file JSON, API).
- Elenca le differenze rispetto al modello di dominio (nomi, tipi, campi extra/mancanti).
- Scrivi
dtoToStrumentoche gestisce i casi mancanti con valori di default. - Scrivi
strumentoToDtoper la direzione inversa. - Usa le funzioni solo dentro l’adapter — mai nei casi d’uso o nel dominio.
- Testa con un DTO incompleto: il mapping produce un oggetto usabile?
Attività in classe
## Mapping nel mio Registro
Formato esterno usato (localStorage / JSON / API):
Differenze rispetto al modello di dominio:
Funzione dtoToStrumento scritta (sì/no):
- Gestisce campo mancante: sì/no
- Gestisce stato non valido: sì/no
Funzione strumentoToDto scritta (sì/no):
Le funzioni stanno nell'adapter (sì/no):
Test con DTO incompleto: risultato:
Spiegazione guidata per studiare
Il mapping è il punto di traduzione tra due “lingue”: il formato del sistema esterno e il modello del dominio. Senza mapping, il formato esterno entra nel dominio e il dominio comincia a conoscere dettagli tecnici che non lo riguardano.
Il punto centrale è: il mapping sta nell’adapter, non nel dominio. Il dominio parla solo di Strumento con nome, categoria e stato. Non sa che fuori esiste un’API che usa name, category e status. Questa ignoranza è intenzionale e va protetta.
Il rischio tipico è usare direttamente l’oggetto ricevuto dall’API nel dominio. Funziona finché l’API non cambia formato. Quando l’API aggiorna un campo da status a current_status, il dominio si rompe. Con il mapping, si aggiorna solo la funzione dtoApiToStrumento — il dominio non sa che è successo nulla.
Passo per passo nel progetto
- Apri l’adapter localStorage (o API) del tuo progetto.
- Cerca dove vengono letti i dati: vengono usati direttamente nel caso d’uso o trasformati prima?
- Se non esiste, crea
dtoToStrumentocon gestione dei campi mancanti. - Verifica che il caso d’uso riceva sempre un oggetto nel formato del dominio — mai il DTO grezzo.
- Testa: cambia il nome di un campo nel DTO salvato in localStorage — il Registro funziona ancora?
Esempio da leggere lentamente
// Adapter con mapping interno — il caso d'uso riceve sempre Strumento
function creaArchivioStrumentiLocalStorage() {
const CHIAVE = "registro:strumenti";
function leggiTutti() {
try {
const raw = JSON.parse(localStorage.getItem(CHIAVE) || "[]");
return raw.map(dtoToStrumento).filter(s => s && s.id);
// ↑ mapping qui dentro — il caso d'uso non vede mai il DTO grezzo
} catch {
return [];
}
}
function salva(strumento) {
try {
const lista = leggiTutti();
const dto = strumentoToDto(strumento); // ← mapping inverso prima di salvare
const i = lista.findIndex(s => s.id === strumento.id);
if (i >= 0) lista[i] = dto; else lista.push(dto);
localStorage.setItem(CHIAVE, JSON.stringify(lista));
} catch {
throw new Error("ARCHIVIO_NON_DISPONIBILE");
}
}
return { leggiTutti, trovaPerId: id => leggiTutti().find(s => s.id === id) ?? null, salva };
}
Prova di comprensione
- Se l’API cambia il campo
statusincurrent_status, quale file devi modificare? - Perché il dominio non deve mai ricevere un DTO grezzo dall’API?
- Come gestisci un DTO con un campo
statoche contiene un valore sconosciuto?
Errori da evitare
- usare direttamente l’oggetto ricevuto da
fetch()nel caso d’uso senza mapping; - mettere le funzioni di mapping in
domain/— appartengono ainfrastructure/; - non gestire i campi mancanti — dati vecchi in localStorage causeranno crash;
- fare il mapping parzialmente (solo da esterno a dominio, dimenticando la direzione inversa).
Prodotto da consegnare
Può essere:
dtoToStrumentoestrumentoToDtoscritte e usate nell’adapter;- un test con un DTO incompleto che verifica il comportamento del mapping;
- una nota nel README che descrive il formato esterno e le differenze col dominio.
Checklist di chiusura
- Esiste una funzione che trasforma il DTO nel modello di dominio
- Esiste una funzione inversa per la direzione dominio → DTO
- Le funzioni stanno nell’adapter, non nel dominio
- I campi mancanti producono valori di default sensati, non errori
- Il caso d’uso riceve sempre un oggetto nel formato del dominio
Domande per la revisione
- Dove nel tuo progetto avviene la trasformazione dal formato salvato al formato del dominio?
- Se cambiassi il nome di un campo nel DTO, quanti file dovresti modificare?
- Il tuo adapter gestisce i dati corrotti o vecchi senza bloccare il Registro?
Risultato atteso
Alla fine lo studente deve saper dire:
Il formato esterno del mio Registro differisce dal dominio per: [differenze].
La funzione dtoToStrumento gestisce i campi mancanti restituendo [valori di default].
Il mapping avviene in [file/adapter] — il caso d'uso non vede mai il DTO grezzo.
Ho verificato con [test/scenario] che il mapping funziona anche con dati incompleti.
Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.