modulo 11 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

Convertire input utente in comandi per i casi d'uso

Come leggere i valori dai form del Registro, normalizzarli, assemblarli in un oggetto comando pulito e passarlo al caso d'uso senza logica di dominio nella UI.

30 minArchitettura del software

Convertire input utente in comandi per i casi d’uso

Questa lezione appartiene al Modulo 11 — Presentation: l’interfaccia come bordo del sistema.

Cos’è un comando

Un comando è un oggetto semplice che trasporta i dati dall’interfaccia al caso d’uso. Non è un’azione — è solo un contenitore di dati.

// Comando per registrare un prestito
const comando = {
  strumentoId: "str-042",
  assegnatario: "Luca 3A"
};

// Comando per registrare uno strumento
const comando = {
  id: crypto.randomUUID(),
  nome: "Trapano elettrico",
  categoria: "Elettrico"
};

Il comando non sa nulla di regole del dominio, di localStorage, di HTML. È solo un oggetto con i dati necessari al caso d’uso.

Il flusso: form → comando → caso d’uso

1. Utente compila il form
2. UI legge i valori (input.value)
3. UI normalizza (trim, lowercase)
4. UI valida il formato (non vuoto, lunghezza)
5. UI costruisce il comando
6. UI passa il comando al caso d'uso
7. Caso d'uso esegue l'operazione
8. UI mostra il risultato

La UI non esegue l’operazione — la prepara e la delega.

Esempio completo — form registrazione strumento

<!-- Form nella UI -->
<form id="form-registra-strumento">
  <input id="nome-strumento" type="text" placeholder="Nome strumento" />
  <select id="categoria-strumento">
    <option value="">Seleziona categoria</option>
    <option value="Elettrico">Elettrico</option>
    <option value="Manuale">Manuale</option>
    <option value="Misura">Misura</option>
  </select>
  <button type="submit">Registra</button>
</form>
// presentation/formRegistraStrumento.js
function avviaFormRegistraStrumento({ registraStrumento }) {
  const form = document.getElementById("form-registra-strumento");

  form.addEventListener("submit", async (evento) => {
    evento.preventDefault();

    // PASSO 1: leggi i valori grezzi
    const nomeGrezzo = document.getElementById("nome-strumento").value;
    const categoriaGrezzo = document.getElementById("categoria-strumento").value;

    // PASSO 2: normalizza (trim)
    const nome = nomeGrezzo.trim();
    const categoria = categoriaGrezzo.trim();

    // PASSO 3: validazione formato nella UI (feedback immediato)
    if (nome.length < 2) {
      mostraErrore("Il nome deve avere almeno 2 caratteri.");
      return;
    }
    if (!categoria) {
      mostraErrore("Seleziona una categoria.");
      return;
    }

    // PASSO 4: costruisci il comando — oggetto pulito
    const comando = {
      id: crypto.randomUUID(),
      nome,       // già normalizzato
      categoria   // già normalizzato
    };

    // PASSO 5: passa al caso d'uso — nessuna logica di dominio qui
    try {
      await registraStrumento(comando);
      mostraSuccesso("Strumento registrato.");
      form.reset();
    } catch (errore) {
      mostraErrore(traduciErrore(errore.message));
    }
  });
}

Esempio completo — form apertura prestito

// presentation/formRegistraPrestito.js
function avviaFormRegistraPrestito({ registraPrestito }) {
  const form = document.getElementById("form-registra-prestito");

  form.addEventListener("submit", async (evento) => {
    evento.preventDefault();

    const strumentoId = document.getElementById("strumento-id").value.trim();
    const assegnatario = document.getElementById("assegnatario").value.trim();

    // Validazione formato — non logica di dominio
    if (!strumentoId) {
      mostraErrore("Inserisci l'ID dello strumento.");
      return;
    }
    if (!assegnatario) {
      mostraErrore("Inserisci l'assegnatario.");
      return;
    }

    // Comando pulito
    const comando = { strumentoId, assegnatario };

    try {
      await registraPrestito(comando);
      mostraSuccesso("Prestito registrato.");
      form.reset();
    } catch (errore) {
      mostraErrore(traduciErrore(errore.message));
    }
  });
}

Cosa NON mettere nel comando

Il comando deve contenere solo dati grezzi normalizzati. Non deve contenere:

// ❌ Logica di dominio nel comando — sbagliato
const comando = {
  strumentoId,
  assegnatario,
  stato: strumento.stato === "disponibile" ? "in_prestito" : "errore", // ← regola del dominio
  dataApertura: new Date().toISOString() // ← il caso d'uso calcola la data
};

// ✅ Solo dati — corretto
const comando = {
  strumentoId,
  assegnatario
  // Il caso d'uso aggiunge la data, verifica lo stato, applica le regole
};

Regola pratica: il comando contiene solo ciò che l’utente ha inserito o selezionato, normalizzato. Tutto il resto lo calcola il caso d’uso.

La differenza tra validazione UI e validazione del caso d’uso

Validazione UI Validazione caso d’uso
Formato (non vuoto, lunghezza) Regole di dominio (strumento disponibile?)
Feedback immediato Controllo definitivo
Previene la chiamata inutile Protegge le invarianti
Opzionale ma utile Obbligatoria

Entrambe esistono. La UI valida il formato per comodità. Il caso d’uso valida le regole per correttezza.

Composizione in main.js

// main.js — composizione
const archivioStrumenti = creaArchivioStrumentiLocalStorage();
const archivioPrestiti = creaArchivioPrestitiLocalStorage();

const registraStrumento = creaRegistraStrumento({ archivioStrumenti });
const registraPrestito = creaRegistraPrestito({ archivioStrumenti, archivioPrestiti });

// Avvia i form passando i casi d'uso già costruiti
avviaFormRegistraStrumento({ registraStrumento });
avviaFormRegistraPrestito({ registraPrestito });

Il form non costruisce il caso d’uso — lo riceve. Questo rende il form testabile (si può passare un caso d’uso finto).

Procedura guidata

  1. Apri un form del Registro che usa localStorage o applica regole direttamente.
  2. Identifica i valori che l’utente inserisce.
  3. Aggiungi .trim() su ogni valore stringa.
  4. Aggiungi validazione del formato (non vuoto, lunghezza minima).
  5. Costruisci l’oggetto comando con i valori normalizzati.
  6. Chiama il caso d’uso con il comando — nessuna logica di dominio nel form.
  7. Gestisci l’esito con mostraSuccesso / mostraErrore(traduciErrore(...)).

Attività in classe

## Convertire input in comando

Form analizzato:

Campi dell'input:
  - [campo]: valore letto da [elemento HTML]

Normalizzazione applicata:
  - [campo]: .trim() / .toLowerCase() / altro

Validazione formato aggiunta:
  - [campo]: [controllo]

Oggetto comando risultante:
  {
    [campo]: valore,
    [campo]: valore
  }

Cosa il caso d'uso NON riceve nel comando (e calcola da solo):

Verifica: il caso d'uso funziona con dati validi e lancia l'errore giusto con dati non validi.

Spiegazione guidata per studiare

Il comando è il contratto tra la UI e il caso d’uso. Chi costruisce il comando (la UI) non sa come verrà usato. Chi usa il comando (il caso d’uso) non sa da dove viene. Questa separazione permette di sostituire l’una senza toccare l’altro.

Il punto centrale è: il comando porta dati, non decisioni. Se nel comando ci sono condizioni (if stato === "disponibile"), la UI sta prendendo decisioni che appartengono al dominio. Il comando deve essere passivo — trasporta solo ciò che l’utente ha inserito.

Il rischio tipico è aggiungere calcoli al comando per “preparare meglio” il dato. Esempio: calcolare la data di apertura nel form invece che nel caso d’uso. Se domani il caso d’uso deve usare un orologio diverso (es. timestamp del server), il form deve cambiare — dipendenza nascosta.

Passo per passo nel progetto

  1. Apri ogni form del Registro (presentation/).
  2. Cerca dove vengono letti i valori (input.value, select.value).
  3. Aggiungi .trim() su ogni lettura.
  4. Cerca se ci sono condizioni o calcoli nel form che appartengono al caso d’uso — spostali.
  5. Verifica che l’oggetto comando contenga solo dati normalizzati dell’utente.
  6. Verifica che il caso d’uso sia chiamato con il solo comando — senza altri argomenti aggiunti nel form.

Esempio da leggere lentamente

// Confronto: form con troppe responsabilità vs form pulito

// ❌ Form che fa troppo
form.addEventListener("submit", () => {
  const strumento = archivioStrumenti.trovaPerId(strumentoId); // ← chiama l'archivio!
  if (strumento.stato !== "disponibile") {                     // ← regola del dominio!
    alert("Non disponibile");
    return;
  }
  const data = new Date().toISOString();                       // ← calcolo del caso d'uso
  const prestito = { strumentoId, assegnatario, data };
  archivioPrestiti.salva(prestito);                            // ← chiama l'infrastruttura!
});

// ✅ Form che fa solo il suo lavoro
form.addEventListener("submit", async () => {
  const comando = {
    strumentoId: document.getElementById("strumento-id").value.trim(),
    assegnatario: document.getElementById("assegnatario").value.trim()
  };
  try {
    await registraPrestito(comando); // ← delega tutto al caso d'uso
    mostraSuccesso("Prestito registrato.");
  } catch (errore) {
    mostraErrore(traduciErrore(errore.message));
  }
});

Il form nella versione corretta non sa nulla di strumenti, stati, archivi. Sa solo leggere input e chiamare un caso d’uso.

Prova di comprensione

  • Cosa contiene un oggetto comando? Cosa non deve contenere?
  • Perché la data di apertura del prestito non deve essere calcolata nel form?
  • Se il form valida il formato (nome non vuoto) e il caso d’uso valida la regola (strumento disponibile), chi ha ragione se i due si contraddicono?

Errori da evitare

  • mettere archivioStrumenti.trovaPerId(...) nel form — chiama l’infrastruttura dalla UI;
  • calcolare valori derivati (date, ID) nel form invece che nel caso d’uso;
  • usare if (stato === "disponibile") nel form — è una regola del dominio;
  • non normalizzare con .trim() — dati con spazi iniziali/finali causano errori silenziosi.

Prodotto da consegnare

Può essere:

  • un form del Registro riscritto che costruisce un comando pulito e lo passa al caso d’uso;
  • la lista di ciò che è stato rimosso dal form (logica di dominio, chiamate all’archivio) con note su dove è stato spostato;
  • un test manuale documentato: il form funziona correttamente con input validi e mostra errori comprensibili con input non validi.

Checklist di chiusura

  • Ogni input.value viene letto con .trim()
  • Il comando contiene solo dati dell’utente — nessun calcolo derivato
  • Il form non chiama archivio.trovaPerIdlocalStorage direttamente
  • Il form non ha condizioni sugli stati degli strumenti
  • Il caso d’uso viene chiamato con il comando come unico argomento di dati
  • Gli errori del caso d’uso vengono tradotti con traduciErrore

Risultato atteso

Alla fine lo studente deve saper dire:

Il form di [operazione] costruisce questo comando:
{ [campi con valori normalizzati] }

Il comando non contiene [cosa] perché [motivo — lo calcola il caso d'uso].
Il form chiama [nomeDelCasoD'uso](comando) e gestisce il risultato.
Ho verificato che il form funziona con dati validi e mostra l'errore corretto con dati non validi.

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