modulo 11 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

La UI come bordo del sistema

Come implementare correttamente la UI come bordo del Registro: legge input, chiama caso d'uso, mostra risultato — con esempi concreti di cosa appartiene alla UI e cosa no.

30 minArchitettura del software

La UI come bordo del sistema

Questa lezione appartiene al Modulo 11 — Collegare la UI ai casi d’uso.

Obiettivo della lezione

Capire il ruolo esatto della UI nel Registro: è il bordo del sistema, non il cuore. Legge l’input dell’utente, chiama il caso d’uso giusto e mostra il risultato. Non contiene regole, non salva dati, non genera ID.

Alla fine sai distinguere cosa appartiene alla UI e cosa no, con esempi concreti tratti dal Registro.

Perché serve nel percorso

La tentazione più comune è scrivere la logica direttamente negli event listener, perché è la cosa più veloce. Dopo tre settimane il progetto diventa ingestibile: non si può testare, non si può modificare una regola senza toccare l’HTML, e ogni bug è nascosto in mezzo al codice del DOM.

Nel Registro del laboratorio

Considera il bottone “Presta strumento”. Cosa fa la UI e cosa non fa?

Cosa fa la UI (corretto):

// src/presentation/azioniPrestito.js
import { registraPrestito } from "../application/registraPrestito.js";
import { archivioStrumenti } from "../infrastructure/archivioStrumentiLocalStorage.js";
import { archivioPrestitiFake } from "../infrastructure/archivioPrestitiLocalStorage.js";

document.getElementById("btn-presta").addEventListener("click", () => {
  const strumentoId = document.getElementById("strumento-selezionato").value;
  const studenteId = document.getElementById("studente-id").value.trim();

  if (!strumentoId || !studenteId) {
    mostraMessaggio("Seleziona uno strumento e inserisci l'identificativo studente.", "errore");
    return;
  }

  try {
    registraPrestito({ strumentoId, studenteId }, archivioStrumenti, archivioPrestitiFake);
    mostraMessaggio("Prestito registrato.", "successo");
    aggiornaListaStrumenti(); // aggiorna la vista
  } catch (err) {
    mostraMessaggio(traduciErrore(err.message), "errore");
  }
});

Cosa NON deve fare la UI (sbagliato):

// SBAGLIATO — la UI che fa tutto
document.getElementById("btn-presta").addEventListener("click", () => {
  const strumentoId = document.getElementById("strumento-selezionato").value;

  // SBAGLIATO: la UI legge da localStorage direttamente
  const strumenti = JSON.parse(localStorage.getItem("registro_strumenti") || "[]");
  const strumento = strumenti.find(s => s.id === strumentoId);

  // SBAGLIATO: la UI controlla le regole
  if (strumento.stato !== "disponibile") {
    alert("Strumento non disponibile");
    return;
  }

  // SBAGLIATO: la UI salva direttamente
  strumento.stato = "in_prestito";
  localStorage.setItem("registro_strumenti", JSON.stringify(strumenti));
  alert("Prestito registrato");
});

Il secondo esempio sembra funzionare ma è impossibile da testare, impossibile da modificare e impossibile da riusare.

Procedura guidata

  1. Apri il file della UI che gestisce le azioni sugli strumenti.
  2. Identifica ogni event listener e chiediti: “questa riga tocca localStorage, regole o generazione di ID?”
  3. Se sì, quella riga non appartiene alla UI — spostala nel livello corretto.
  4. Controlla che ogni event listener faccia solo: leggi input → chiama caso d’uso → mostra risultato.
  5. Verifica che nessun import nella UI venga da localStorage direttamente.

Attività in classe

Classifica ognuna di queste righe come “UI corretta”, “troppo nella UI” o “dipende dal contesto”:

const nome = form.elements["nome"].value.trim();                     // ??
const id = crypto.randomUUID();                                       // ??
if (strumento.stato !== "disponibile") throw new Error("...");       // ??
document.getElementById("lista").innerHTML = "";                      // ??
localStorage.setItem("strumenti", JSON.stringify(strumenti));        // ??
mostraMessaggio("Strumento registrato.", "successo");                 // ??
registraStrumento({ nome, categoria }, archivio);                    // ??

Poi compila la scheda:

## La UI come bordo

Event listener analizzati:

Righe spostate fuori dalla UI:

File di destinazione:

Comportamento verificato dopo lo spostamento:

Spiegazione guidata per studiare

Il bordo del sistema è il punto in cui il mondo esterno (utente, browser, API) entra nel sistema. La UI è un bordo di input/output: riceve comandi dagli utenti e mostra risultati.

Il dominio e i casi d’uso stanno al centro: non sanno nulla di HTML, DOM, localStorage, fetch o alert. Questa ignoranza è il loro punto di forza — possono essere testati da soli, spostati su un server, o usati da una CLI.

Regola pratica per la UI del Registro:

  • Può leggere: valori di form, selezioni, click — tutto l’input dell’utente.
  • Può scrivere: textContent, className, innerHTML — tutto l’output visivo.
  • Può chiamare: i casi d’uso con i parametri letti dall’input.
  • Non può contenere: controlli sullo stato degli strumenti, generazione di ID, scrittura in localStorage, logica di calcolo.
// Struttura corretta di ogni handler della UI del Registro
function handleAzione(evento) {
  // 1. Leggi input
  const parametri = leggiInput(evento);

  // 2. Valida input (solo formato, non regole)
  if (!parametriValidi(parametri)) {
    mostraErroreUI("...");
    return;
  }

  // 3. Chiama caso d'uso
  try {
    casoD'uso(parametri, dipendenze);
    mostraSuccesso("...");
    aggiornaVista();
  } catch (err) {
    mostraErroreUI(traduciErrore(err.message));
  }
}

Passo per passo nel progetto

  1. Elenca tutti i file in src/presentation/.
  2. Per ogni file, elenca i metodi o le funzioni.
  3. Per ogni funzione, scrivi in una riga cosa fa.
  4. Identifica le funzioni che contengono più di una delle tre responsabilità (input/chiama/mostra).
  5. Sposta la logica in eccesso nel livello corretto.
  6. Verifica che i test dei casi d’uso passino ancora.

Prova di comprensione

  • Qual è l’unica responsabilità della UI nel Registro?
  • Un event listener che chiama localStorage.getItem è nella UI o nell’infrastruttura?
  • Dove deve stare il controllo “uno strumento guasto non può essere prestato”?
  • Come si verifica che la UI non contenga logica di dominio?

Errori da evitare

  • fare il check strumento.stato === "disponibile" nella UI invece che nel caso d’uso;
  • importare localStorage direttamente in un file della UI;
  • generare crypto.randomUUID() nell’event listener invece che nel caso d’uso;
  • usare alert() per i messaggi — appartiene alla UI ma è da evitare per accessibilità.

Prodotto da consegnare

Tutti gli event listener del Registro che rispettano il pattern input/chiama/mostra, senza logica di dominio o accesso diretto a localStorage. Almeno un caso d’uso verificabile senza il browser.

Checklist di chiusura

  • nessun file in src/presentation/ importa localStorage direttamente
  • nessun event listener contiene regole sullo stato degli strumenti o dei prestiti
  • ogni handler della UI segue il pattern: leggi → chiama → mostra
  • i messaggi di errore sono tradotti in italiano leggibile
  • almeno un caso d’uso è testabile senza aprire il browser

Risultato atteso

Ho verificato tutti gli event listener del Registro.
Ogni handler legge input, chiama il caso d'uso e mostra il risultato.
Nessun handler tocca localStorage direttamente o contiene regole di dominio.
Ho spostato in src/application/ le regole che erano nella UI.
Il prossimo passo sarebbe aggiungere test per i casi d'uso ora separati.