modulo 09 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

Passare le dipendenze al caso d'uso

Rendere esplicite le dipendenze dei casi d'uso ricevendo archivi e servizi dall'esterno invece di crearli dentro l'operazione.

30 min Architettura del software

Passare le dipendenze al caso d’uso

Un caso d’uso ha bisogno di collaboratori.

Per esempio RegistraPrestito ha bisogno di:

  • archivio strumenti;
  • archivio prestiti;
  • funzioni del dominio;
  • forse un generatore di id o una data, se vogliamo renderli controllabili.

La domanda e:

chi crea questi collaboratori?

Nel percorso completo vogliamo evitare che il caso d’uso li crei da solo.

Obiettivo

Alla fine devi avere casi d’uso che ricevono le dipendenze dall’esterno.

Questo rende piu facile:

  • cambiare archivio;
  • scrivere test;
  • usare archivi finti;
  • separare Application da Infrastructure;
  • capire di cosa ha bisogno ogni operazione.

Forma fragile

Esempio:

import { creaArchivioStrumentiInMemoria } from "../infrastructure/archivioStrumentiInMemoria.js";

export function registraStrumento(comando) {
  const archivioStrumenti = creaArchivioStrumentiInMemoria();
  const strumento = creaStrumento(comando);

  archivioStrumenti.salva(strumento);

  return strumento;
}

Il problema:

ogni chiamata crea un nuovo archivio

e soprattutto:

il caso d'uso sceglie l'implementazione concreta

Forma migliore

export function creaRegistraStrumento({ archivioStrumenti }) {
  return function registraStrumento(comando) {
    const strumento = creaStrumento(comando);
    archivioStrumenti.salva(strumento);
    return strumento;
  };
}

Il caso d’uso riceve l’archivio.

Non lo crea.

Questa forma rende la dipendenza visibile:

RegistraStrumento dipende da archivioStrumenti

Perche usare una funzione che crea il caso d’uso

Potremmo scrivere:

export function registraStrumento(comando, archivioStrumenti) {
  // ...
}

Funziona.

Pero spesso diventa scomodo passare le stesse dipendenze a ogni chiamata.

Con questa forma:

const registraStrumento = creaRegistraStrumento({
  archivioStrumenti,
});

prepariamo il caso d’uso una volta.

Poi la UI chiama:

registraStrumento(comando);

La UI non deve passare sempre l’archivio.

Esempio con RegistraPrestito

export function creaRegistraPrestito({
  archivioStrumenti,
  archivioPrestiti,
}) {
  return function registraPrestito(comando) {
    const strumento = archivioStrumenti.trovaPerId(comando.strumentoId);

    if (!strumento) {
      throw new Error("STRUMENTO_NON_TROVATO");
    }

    verificaStrumentoPrestabile(strumento);

    const prestiti = archivioPrestiti.tutti();

    if (esistePrestitoApertoPerStrumento(prestiti, strumento.id)) {
      throw new Error("STRUMENTO_GIA_IN_PRESTITO");
    }

    // crea, aggiorna, salva
  };
}

Il caso d’uso dichiara chiaramente cio che usa.

Dipendenze obbligatorie

Se una dipendenza manca, e meglio fallire subito.

Esempio semplice:

export function creaRegistraStrumento({ archivioStrumenti }) {
  if (!archivioStrumenti) {
    throw new Error("ARCHIVIO_STRUMENTI_OBBLIGATORIO");
  }

  return function registraStrumento(comando) {
    // ...
  };
}

Non e sempre necessario, ma e utile didatticamente.

Rende esplicito che il caso d’uso non funziona da solo.

Test piu facili

In un test puoi passare un archivio in memoria:

const archivioStrumenti = creaArchivioStrumentiInMemoria();

const registraStrumento = creaRegistraStrumento({
  archivioStrumenti,
});

Oppure un fake:

const archivioStrumenti = {
  salvati: [],
  salva(strumento) {
    this.salvati.push(strumento);
    return strumento;
  },
};

Il caso d’uso non cambia.

Dipendenze da non passare ancora

Non esagerare.

In questa fase non serve passare tutto dall’esterno.

Possiamo rimandare:

  • logger;
  • configurazioni complesse;
  • servizi remoti;
  • autorizzazioni complete;
  • gestione transazioni.

Passiamo le dipendenze che servono davvero ai casi d’uso attuali.

Esercizio

Scegli un caso d’uso che importa o crea direttamente un archivio.

Trasformalo:

Prima:

export function casoUso(comando) {
  const archivio = creaArchivioConcreto();
}

Dopo:

export function creaCasoUso({ archivio }) {
  return function casoUso(comando) {
    // usa archivio
  };
}

Poi aggiorna il punto in cui il caso d’uso viene creato.

Cosa produrre

Alla fine della lezione devi avere:

  • almeno un caso d’uso che riceve dipendenze dall’esterno;
  • lista delle dipendenze richieste;
  • nessuna creazione di archivio dentro quel caso d’uso;
  • uno scenario o test che passa un archivio diverso;
  • nota sul perche il caso d’uso e piu sostituibile.

Spiegazione guidata per studiare

Questa lezione va studiata su due piani. Il primo è il comportamento visibile del Registro del laboratorio: che cosa vede o fa l’utente quando registra uno strumento, cerca un dispositivo, apre un prestito o segnala un guasto. Il secondo è il confine architetturale: dove deve stare il codice che rende possibile quel comportamento.

Per passare le dipendenze al caso d’uso, il punto centrale è questo: Un caso d’uso coordina un’operazione completa: riceve un comando, legge ciò che serve, applica regole, salva il risultato e restituisce un esito.

Il rischio tipico è far diventare il caso d’uso una seconda UI o un secondo database. Deve coordinare, non mostrare HTML e non conoscere dettagli tecnici di salvataggio. Per evitarlo, non partire dal file che hai già aperto nell’editor. Parti dalla domanda: “quale responsabilità sto modificando?”. Se la risposta riguarda una regola del laboratorio, guarda il dominio. Se riguarda una sequenza di operazioni, guarda i casi d’uso. Se riguarda salvataggio, file, localStorage, API o database, guarda l’infrastruttura. Se riguarda input, pulsanti, form e messaggi, guarda la Presentation.

Il tempo è sufficiente per alternare spiegazione, modifica guidata e revisione. Alla fine della lezione lo studente non deve ricordare una definizione a memoria: deve saper indicare un punto del progetto, spiegare perché quel codice sta lì e mostrare una verifica del comportamento.

Passo per passo nel progetto

  1. Rileggi il titolo della lezione e riscrivilo come domanda pratica. Per esempio: “dove deve stare questa regola?”, “chi deve salvare questo dato?”, “come verifico questo errore?”.
  2. Apri solo i file necessari. Se devi toccare più di tre file, fermati e scrivi prima una mini-mappa.
  3. Definisci input e output del caso d’uso.
  4. Leggi i dati attraverso un archivio o una dipendenza esplicita.
  5. Chiama le regole del dominio.
  6. Salva il risultato senza sapere come funziona la persistenza concreta.
  7. Aggiorna una nota breve nel README o negli appunti se hai cambiato un confine del progetto.

Esempio da leggere lentamente

Questo esempio serve a ragionare, non a incollare codice senza capire:

function registraPrestito(comando, archivioStrumenti, archivioPrestiti) {
  const strumento = archivioStrumenti.trovaPerId(comando.strumentoId);
  verificaStrumentoPrestabile(strumento);
  const prestito = apriPrestito(strumento.id, comando.nomePersona);
  archivioPrestiti.salva(prestito);
  archivioStrumenti.salva({ ...strumento, stato: "in_prestito" });
  return prestito;
}

Leggilo chiedendoti che cosa cambierebbe se domani sostituissimo la pagina web con una CLI, oppure localStorage con un’API. Se l’esempio continua ad avere senso senza riscrivere tutto, il confine è probabilmente buono. Se invece un dettaglio tecnico si propaga in molti file, il progetto sta tornando fragile.

Prova di comprensione

Prima di chiudere la lezione, rispondi per iscritto a queste domande:

  • Qual è la responsabilità principale trattata in questa lezione?
  • In quale livello del progetto dovrebbe stare?
  • Quale errore faresti se cercassi la soluzione più veloce?
  • Quale piccolo test, controllo manuale o esempio dimostra che hai capito?
  • Che cosa deve restare facile da cambiare dopo questa lezione?

Una risposta accettabile non deve essere lunga. Deve però usare parole concrete del Registro: strumento, prestito, stato, archivio, caso d’uso, messaggio, test.

Controllo finale

La lezione e completata quando puoi rispondere si:

  • il caso d’uso non crea l’archivio;
  • le dipendenze sono visibili nella funzione di creazione;
  • posso passare un archivio in memoria;
  • posso passare un archivio finto nei test;
  • la UI chiama il caso d’uso senza conoscere i dettagli dell’archivio.

Nella prossima lezione diamo un nome a questa idea: inversione del controllo.