modulo 09 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

Usare un archivio in memoria attraverso l'interfaccia

Adattare l'archivio in memoria al contratto ArchivioStrumenti e usarlo senza legare i casi d'uso alla sua implementazione.

30 min Architettura del software

Usare un archivio in memoria attraverso l’interfaccia

Abbiamo definito il contratto ArchivioStrumenti.

Ora verifichiamo che l’archivio in memoria lo rispetti.

L’obiettivo non e scrivere un archivio complicato.

L’obiettivo e fare in modo che il caso d’uso non debba sapere che l’archivio e in memoria.

Obiettivo

Alla fine devi avere:

  • un archivio in memoria che rispetta il contratto;
  • un caso d’uso che lo usa tramite metodi minimi;
  • una piccola verifica manuale o test;
  • una lista di dettagli dell’archivio che il caso d’uso non conosce.

Contratto da rispettare

Ricordiamo:

ArchivioStrumenti
- salva(strumento)
- trovaPerId(id)
- tutti()

Qualsiasi archivio che offre queste operazioni puo essere usato dai casi d’uso.

Implementazione in memoria

File:

src/infrastructure/archivioStrumentiInMemoria.js

Codice:

export function creaArchivioStrumentiInMemoria(datiIniziali = []) {
  const strumenti = [...datiIniziali];

  return {
    salva(strumento) {
      const indice = strumenti.findIndex((item) => item.id === strumento.id);

      if (indice === -1) {
        strumenti.push(strumento);
      } else {
        strumenti[indice] = strumento;
      }

      return strumento;
    },

    trovaPerId(id) {
      return strumenti.find((strumento) => strumento.id === id) ?? null;
    },

    tutti() {
      return [...strumenti];
    },
  };
}

Questo archivio usa un array interno.

Il caso d’uso non deve saperlo.

Uso dal caso d’uso

Esempio:

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

Qui il caso d’uso non chiama:

strumenti.push(...)

Chiama:

archivioStrumenti.salva(strumento)

Questa e la differenza importante.

Verifica manuale

Puoi scrivere:

const archivio = creaArchivioStrumentiInMemoria();

archivio.salva({
  id: "s-001",
  nome: "Arduino Uno",
  categoria: "microcontrollori",
  stato: "disponibile",
});

console.log(archivio.trovaPerId("s-001"));
console.log(archivio.tutti());

Risultato atteso:

trovaPerId restituisce Arduino Uno
tutti restituisce una lista con Arduino Uno

Poi aggiorna:

archivio.salva({
  id: "s-001",
  nome: "Arduino Uno",
  categoria: "microcontrollori",
  stato: "in_prestito",
});

Risultato atteso:

lo stesso id non crea un duplicato
lo stato diventa in_prestito

Dettagli nascosti

Il caso d’uso non sa:

  • che i dati sono in un array;
  • che salva fa push o update;
  • che tutti restituisce una copia;
  • che i dati spariscono al refresh;
  • come viene cercato l’id.

Questi dettagli stanno in Infrastructure.

Interfaccia non significa magia

In JavaScript l’interfaccia non e obbligatoriamente una parola chiave.

E un accordo:

se hai questi metodi, puoi essere usato come ArchivioStrumenti

Questo e sufficiente per il laboratorio.

Piu avanti TypeScript puo rendere l’accordo esplicito nel codice.

ArchivioPrestiti

Fai lo stesso per i prestiti:

export function creaArchivioPrestitiInMemoria(datiIniziali = []) {
  const prestiti = [...datiIniziali];

  return {
    salva(prestito) {
      const indice = prestiti.findIndex((item) => item.id === prestito.id);

      if (indice === -1) {
        prestiti.push(prestito);
      } else {
        prestiti[indice] = prestito;
      }

      return prestito;
    },

    trovaPerId(id) {
      return prestiti.find((prestito) => prestito.id === id) ?? null;
    },

    tutti() {
      return [...prestiti];
    },
  };
}

La forma e simile.

Non estrarre subito una fabbrica generica se rende il codice meno chiaro per la classe.

Esercizio

Compila:

# Verifica ArchivioStrumentiInMemoria

Metodo: salva
Scenario:
Risultato atteso:

Metodo: trovaPerId
Scenario:
Risultato atteso:

Metodo: tutti
Scenario:
Risultato atteso:

Dettagli nascosti al caso d'uso:
- ...

Poi verifica che RegistraStrumento usi solo il contratto.

Cosa produrre

Alla fine della lezione devi avere:

  • archivio in memoria conforme al contratto;
  • verifica di salva, trovaPerId, tutti;
  • caso d’uso che usa l’archivio senza conoscere l’array interno;
  • nota sui dettagli nascosti;
  • eventuale archivio prestiti coerente.

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 usare un archivio in memoria attraverso l’interfaccia, il punto centrale è questo: L’infrastruttura è il bordo tecnico: salva, legge, chiama API, usa localStorage o file JSON. È importante, ma deve restare sostituibile.

Il rischio tipico è far entrare il formato salvato dentro il dominio. Un oggetto comodo per localStorage non è automaticamente il modello con cui ragiona il progetto. 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. Distingui modello di dominio e formato salvato.
  4. Crea una funzione di mapping leggibile.
  5. Gestisci dati mancanti o non validi.
  6. Verifica che i casi d’uso non cambino quando cambi archivio.
  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 strumentoToDTO(strumento) {
  return {
    id: strumento.id,
    name: strumento.nome,
    category: strumento.categoria,
    status: strumento.stato
  };
}

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:

  • l’archivio in memoria rispetta il contratto;
  • il caso d’uso non usa array globali;
  • salva inserisce o aggiorna;
  • trovaPerId restituisce null se non trova;
  • posso sostituire l’archivio se mantengo gli stessi metodi.

Nella prossima lezione passiamo esplicitamente le dipendenze al caso d’uso.