modulo 09 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

Inversione del controllo senza framework

Capire l'inversione del controllo in modo pratico: il caso d'uso non sceglie le implementazioni, le riceve dall'esterno.

30 min Architettura del software

Inversione del controllo senza framework

Inversione del controllo sembra un termine grande.

Nel nostro laboratorio significa una cosa semplice:

Il caso d'uso non decide quale archivio usare.
Qualcun altro glielo passa.

Non serve un framework.

Non serve un container.

Serve solo rendere esplicito dove vengono create e collegate le dipendenze.

Obiettivo

Alla fine devi saper spiegare inversione del controllo con un esempio del Registro del laboratorio.

Devi anche saper riconoscere:

  • chi decide l’implementazione concreta;
  • chi riceve una dipendenza;
  • perche questo rende il codice piu testabile e modificabile.

Controllo normale

Forma rigida:

export function registraPrestito(comando) {
  const archivioStrumenti = creaArchivioStrumentiLocalStorage();
  const archivioPrestiti = creaArchivioPrestitiLocalStorage();

  // usa gli archivi
}

Qui il caso d’uso controlla tutto:

  • sceglie l’archivio;
  • lo crea;
  • lo usa.

Se vogliamo cambiare archivio, tocchiamo il caso d’uso.

Controllo invertito

Forma flessibile:

export function creaRegistraPrestito({
  archivioStrumenti,
  archivioPrestiti,
}) {
  return function registraPrestito(comando) {
    // usa gli archivi ricevuti
  };
}

Qui il caso d’uso non crea gli archivi.

Li riceve.

Il controllo sulla scelta dell’implementazione si sposta fuori.

Questa e l’inversione.

Dove va il controllo

Il controllo va nel punto di composizione.

Esempio:

const archivioStrumenti = creaArchivioStrumentiInMemoria();
const archivioPrestiti = creaArchivioPrestitiInMemoria();

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

Se domani cambiamo archivio:

const archivioStrumenti = creaArchivioStrumentiLocalStorage();
const archivioPrestiti = creaArchivioPrestitiLocalStorage();

Il caso d’uso resta uguale.

Non serve un framework

Molti framework hanno strumenti per la dependency injection.

In questo corso non ci servono.

Useremo funzioni e oggetti:

const casoUso = creaCasoUso({
  dipendenzaUno,
  dipendenzaDue,
});

Questa forma e leggibile, esplicita e sufficiente.

Prima capire il concetto.

Poi, se un giorno userai framework, saprai cosa stanno automatizzando.

Benefici pratici

Inversione del controllo ci permette di:

  • cambiare archivio;
  • testare con fake;
  • evitare import concreti nei casi d’uso;
  • rendere visibili le dipendenze;
  • ridurre accoppiamento tra Application e Infrastructure.

Non e teoria astratta.

Serve quando il progetto deve cambiare.

Rischio: farne troppo

Non trasformare ogni funzione in una fabbrica complicata.

Esempio eccessivo:

creaCreaCreaVerificaNome({ normalizzatore, validatore, configurazione })

Per un laboratorio scolastico sarebbe rumore.

Usa inversione del controllo dove c’e un motivo concreto:

  • archivio sostituibile;
  • generatore di id controllabile;
  • data controllabile nei test;
  • servizio esterno.

Esempio con generatore di id

In alcuni casi possiamo passare anche un generatore di id:

export function creaRegistraStrumento({
  archivioStrumenti,
  generaId,
}) {
  return function registraStrumento(comando) {
    const strumento = creaStrumento({
      ...comando,
      id: comando.id ?? generaId(),
    });

    archivioStrumenti.salva(strumento);
    return strumento;
  };
}

Nei test:

const generaId = () => "s-test-001";

Il test diventa prevedibile.

Questo e un uso sensato dell’inversione del controllo.

Esercizio

Rispondi:

Chi sceglie l'archivio in questa versione?

Codice A:

export function registraStrumento(comando) {
  const archivio = creaArchivioStrumentiLocalStorage();
}

Codice B:

export function creaRegistraStrumento({ archivioStrumenti }) {
  return function registraStrumento(comando) {
    archivioStrumenti.salva(comando);
  };
}

Risposta:

A: sceglie il caso d'uso.
B: sceglie il codice che crea il caso d'uso.

Scheda

Compila:

# Inversione del controllo

Caso d'uso:

Dipendenze ricevute:
- ...

Dipendenze che prima erano create dentro:
- ...

Punto in cui vengono create ora:

Vantaggio ottenuto:

Rischio di complicare troppo:

Cosa produrre

Alla fine della lezione devi avere:

  • una spiegazione breve di inversione del controllo;
  • almeno un caso d’uso che riceve archivi;
  • punto di composizione che crea archivi e casi d’uso;
  • esempio di sostituzione archivio;
  • nota su cosa non conviene invertire per ora.

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 inversione del controllo senza framework, 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:

  • so spiegare inversione del controllo senza framework;
  • il caso d’uso non sceglie l’archivio concreto;
  • so dove viene scelta l’implementazione;
  • so perche questo aiuta i test;
  • so evitare astrazioni inutili.

Nella prossima lezione organizziamo la composizione nel punto di avvio.