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.
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
- Apri il file della UI che gestisce le azioni sugli strumenti.
- Identifica ogni event listener e chiediti: “questa riga tocca localStorage, regole o generazione di ID?”
- Se sì, quella riga non appartiene alla UI — spostala nel livello corretto.
- Controlla che ogni event listener faccia solo: leggi input → chiama caso d’uso → mostra risultato.
- Verifica che nessun import nella UI venga da
localStoragedirettamente.
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
- Elenca tutti i file in
src/presentation/. - Per ogni file, elenca i metodi o le funzioni.
- Per ogni funzione, scrivi in una riga cosa fa.
- Identifica le funzioni che contengono più di una delle tre responsabilità (input/chiama/mostra).
- Sposta la logica in eccesso nel livello corretto.
- 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
localStoragedirettamente 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/importalocalStoragedirettamente - 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.