modulo 12 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

Errore infrastrutturale: salvataggio o lettura fallita

Riconoscere e gestire gli errori infrastrutturali nel Registro: fallimenti di localStorage, API o file JSON che devono essere intercettati al bordo del sistema senza raggiungere il dominio.

25 min Architettura del software

Errore infrastrutturale: salvataggio o lettura fallita

Questa lezione appartiene al Modulo 12 — Errori, validazione e messaggi.

Cos’è un errore infrastrutturale

Un errore infrastrutturale nasce quando il sistema non riesce a leggere o scrivere dati — indipendentemente dalle regole del laboratorio e dall’input dell’utente. Il problema sta nel bordo tecnico del sistema: localStorage, un file JSON, un’API remota.

Esempi nel Registro:

  • localStorage è pieno (QuotaExceededError)
  • localStorage non è disponibile (navigazione in incognito su alcuni browser)
  • JSON.parse fallisce perché il dato salvato è corrotto
  • Una chiamata API restituisce un errore HTTP (500, 503, timeout)
  • Il file JSON non esiste o non è leggibile

Dove nasce e dove viene gestito

Infrastructure (adapter)  →  intercetta l'errore tecnico, lo traduce in errore leggibile
Application (caso d'uso)  →  può ricevere l'errore e decidere se propagarlo o gestirlo
Presentation (UI)         →  mostra un messaggio comprensibile all'utente

L’errore infrastrutturale non deve attraversare l’intera applicazione grezzo. Il messaggio QuotaExceededError: Failed to execute 'setItem' on 'Storage' non ha senso per l’utente. L’adapter lo intercetta e rilancia con un codice chiaro.

Come gestirlo nell’adapter localStorage

// infrastructure/archivioStrumentiLocalStorage.js
function salva(strumento) {
  try {
    const lista = leggiTutti();
    const indice = lista.findIndex(s => s.id === strumento.id);
    if (indice >= 0) lista[indice] = strumento;
    else lista.push(strumento);
    localStorage.setItem("registro:strumenti", JSON.stringify(lista));
  } catch (errore) {
    // localStorage pieno, non disponibile, o altro problema tecnico
    throw new Error("ARCHIVIO_NON_DISPONIBILE");
  }
}

function leggiTutti() {
  try {
    const raw = localStorage.getItem("registro:strumenti");
    if (!raw) return [];
    return JSON.parse(raw);
  } catch (errore) {
    // JSON corrotto o localStorage non disponibile
    console.error("Errore lettura archivio:", errore);
    return []; // fallback: restituisce lista vuota invece di propagare l'errore
  }
}

Nota la differenza:

  • salva rilancia con ARCHIVIO_NON_DISPONIBILE — il fallimento è bloccante
  • leggiTutti restituisce [] — il fallimento è recuperabile (preferibile non bloccare il caricamento)

Come gestirlo in un adapter API

// infrastructure/archivioStrumentiAPI.js
async function salva(strumento) {
  try {
    const risposta = await fetch("/api/strumenti", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(strumento)
    });
    if (!risposta.ok) {
      throw new Error("ARCHIVIO_NON_DISPONIBILE");
    }
  } catch (errore) {
    if (errore.message === "ARCHIVIO_NON_DISPONIBILE") throw errore;
    // Network error, timeout, ecc.
    throw new Error("ARCHIVIO_NON_DISPONIBILE");
  }
}

Il codice errore è lo stesso (ARCHIVIO_NON_DISPONIBILE) indipendentemente dal tipo di archivio — localStorage o API. Il caso d’uso non sa quale archivio sta usando.

Come il caso d’uso lo propaga

Il caso d’uso non cattura (di solito) gli errori infrastrutturali — li lascia salire alla Presentation:

// Application
async function registraPrestito(comando) {
  const strumento = await archivioStrumenti.trovaPerId(comando.strumentoId);
  if (!strumento) throw new Error("STRUMENTO_NON_TROVATO");

  // Se salva() lancia ARCHIVIO_NON_DISPONIBILE, l'errore sale fino alla UI
  await archivioPrestiti.salva(prestito);
  await archivioStrumenti.salva({ ...strumento, stato: "in_prestito" });
}
// Presentation
try {
  await registraPrestito(comando);
} catch (errore) {
  const messaggi = {
    STRUMENTO_NON_TROVATO: "Strumento non trovato.",
    STRUMENTO_NON_DISPONIBILE: "Questo strumento non può essere prestato.",
    ARCHIVIO_NON_DISPONIBILE: "Non riesco a salvare i dati. Riprova tra poco."
  };
  mostraMessaggio(messaggi[errore.message] ?? "Operazione non riuscita.");
  console.error("Dettaglio tecnico:", errore);
}

Il console.error mantiene il dettaglio tecnico nel log senza mostrarlo all’utente.

Errori infrastrutturali tipici per tipo di archivio

ArchivioErrore tecnicoCodice da usare
localStorage pienoQuotaExceededErrorARCHIVIO_NON_DISPONIBILE
localStorage assenteTypeError su setItemARCHIVIO_NON_DISPONIBILE
JSON corrottoSyntaxError su JSON.parserestituire [] (fallback)
API non raggiungibileTypeError: Failed to fetchARCHIVIO_NON_DISPONIBILE
API risponde con erroreHTTP 500, 503ARCHIVIO_NON_DISPONIBILE

Procedura guidata

  1. Apri i tuoi adapter (infrastructure/).
  2. Ogni metodo salva e leggiTutti ha un try/catch?
  3. Il catch rilancia con un codice chiaro o restituisce un fallback?
  4. La UI cattura ARCHIVIO_NON_DISPONIBILE e mostra un messaggio comprensibile?
  5. Il dettaglio tecnico viene loggato in console.error (non mostrato all’utente)?

Attività in classe

## Errori infrastrutturali nel mio Registro

Adapter analizzato:

Metodo con possibile errore infrastrutturale:

Tipo di errore tecnico (QuotaExceeded, JSON corrotto, network...):

Strategia scelta (rilancia / fallback):

Codice errore usato:

Messaggio utente corrispondente:

Spiegazione guidata per studiare

Gli errori infrastrutturali sono i più “nascosti” nei progetti scolastici. localStorage quasi sempre funziona — finché non è pieno, o finché l’utente non apre una finestra in incognito. L’errore emerge in produzione o nei casi limite, non durante il test normale.

Il punto centrale è: l’adapter è l’unico responsabile degli errori dell’infrastruttura. Tutto ciò che sta sopra (Application, Domain, Presentation) non deve sapere come funziona localStorage o fetch — deve solo sapere che un’operazione è fallita, con un codice chiaro.

Il rischio tipico è non avere nessun try/catch negli adapter. In quel caso, un QuotaExceededError attraversa tutto il sistema e appare nella UI come errore JavaScript grezzo.

Passo per passo nel progetto

  1. Apri ogni metodo salva e leggiTutti negli adapter.
  2. Aggiungi try/catch dove manca.
  3. Nel catch: rilancia con ARCHIVIO_NON_DISPONIBILE per i fallimenti bloccanti; restituisce [] per le letture recuperabili.
  4. Aggiungi console.error(errore) nel catch per conservare il dettaglio tecnico.
  5. Verifica che la UI mostri un messaggio sensato quando l’archivio non è disponibile.

Esempio da leggere lentamente

// Adapter robusto con gestione errori
function creaArchivioStrumentiLocalStorage() {
  const CHIAVE = "registro:strumenti";

  function leggiTutti() {
    try {
      const raw = localStorage.getItem(CHIAVE);
      if (!raw) return [];
      return JSON.parse(raw);
    } catch (errore) {
      console.error("Impossibile leggere strumenti:", errore);
      return []; // ← fallback: il Registro parte vuoto invece di bloccarsi
    }
  }

  function salva(strumento) {
    try {
      const lista = leggiTutti();
      const i = lista.findIndex(s => s.id === strumento.id);
      if (i >= 0) lista[i] = strumento; else lista.push(strumento);
      localStorage.setItem(CHIAVE, JSON.stringify(lista));
    } catch (errore) {
      console.error("Impossibile salvare strumento:", errore);
      throw new Error("ARCHIVIO_NON_DISPONIBILE"); // ← bloccante: non possiamo procedere
    }
  }

  return { leggiTutti, trovaPerId: (id) => leggiTutti().find(s => s.id === id) ?? null, salva };
}

Prova di comprensione

  • Qual è la differenza tra un errore infrastrutturale e un errore applicativo?
  • Perché leggiTutti può restituire [] come fallback, mentre salva deve rilanciare l’errore?
  • Dove deve stare il console.error — nel dominio, nel caso d’uso o nell’adapter?

Errori da evitare

  • non avere nessun try/catch negli adapter;
  • mostrare messaggi tecnici come QuotaExceededError o SyntaxError all’utente;
  • catturare l’errore infrastrutturale nel dominio (che non deve sapere di localStorage);
  • non loggare il dettaglio tecnico — rende il debugging impossibile.

Prodotto da consegnare

Può essere:

  • un adapter leggiTutti e salva con try/catch completo;
  • un test che simula un archivio che fallisce e verifica che il codice errore corretto sia lanciato;
  • una nota nel README che descrive il comportamento in caso di archivio non disponibile.

Checklist di chiusura

  • Ogni metodo salva negli adapter ha un try/catch
  • Il catch rilancia con ARCHIVIO_NON_DISPONIBILE
  • leggiTutti restituisce [] in caso di errore (non blocca l’avvio)
  • console.error conserva il dettaglio tecnico
  • La UI mostra un messaggio comprensibile per ARCHIVIO_NON_DISPONIBILE

Domande per la revisione

  • I tuoi adapter hanno try/catch? Se no, cosa succede quando localStorage è pieno?
  • Come distingueresti un errore di localStorage da un errore di rete in produzione?
  • Il tuo progetto funziona (anche parzialmente) quando localStorage non è disponibile?

Risultato atteso

Alla fine lo studente deve saper dire:

Il mio adapter [X] gestisce gli errori infrastrutturali con try/catch.
In caso di fallimento lancia [ARCHIVIO_NON_DISPONIBILE] / restituisce [fallback].
La UI mostra: "Non riesco a salvare i dati. Riprova tra poco."
Il dettaglio tecnico viene loggato in console.error.

Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.