modulo 08 / lezione 40

Debugging di dati, backend e integrazioni

Contratti API e schema dei dati

Controllare se richiesta, risposta e modello dati concordano su nomi, tipi, valori mancanti, formati e versione dell'API.

55 min Debugging

In parole semplici

Tra client e server c’è un contratto: nomi dei campi, tipi, valori opzionali, formati delle date, semantica dei codici. Quando il contratto è scritto, condiviso e verificato, le integrazioni sono noiose. Quando è implicito, ogni cambiamento è un bug latente.

Parole nuove

  • contratto API: accordo esplicito tra producer e consumer di un’API.
  • schema: descrizione formale della struttura dei dati (JSON Schema, OpenAPI, GraphQL SDL).
  • breaking change: modifica che rompe i client esistenti.
  • versioning: meccanismo per evolvere l’API senza rompere.

Cosa impari

  • Confrontare richiesta inviata, risposta ricevuta e modello dati atteso.
  • Riconoscere contratti rotti silenziosamente.
  • Usare uno schema (OpenAPI/JSON Schema) per validare.

Tre punti di confronto

[1. CLIENT richiesta] → [2. SERVER risposta] → [3. CLIENT modello]

Ogni transizione può divergere:

  • Client → richiesta: il client costruisce header/body sbagliati.
  • Server → risposta: il server cambia struttura senza preavviso.
  • Risposta → modello: il client legge campi che non esistono o ignora campi che ci sono.

Esempio: una card che mostra vuoti

UI mostra “Nome: ” senza valore. Risposta API:

{
  "user_name": "Carlo",
  "user_age": 35,
  "is_active": true
}

Codice client:

function rendiCard(utente) {
  return `<div>Nome: ${utente.nome}</div>`;  // utente.nome non esiste
}

Bug: la risposta usa user_name (snake_case), il client legge nome. Nessun errore JS: l’accesso a undefined ritorna undefined, che template literal mostra come stringa vuota.

Tre check per trovare il bug

  1. DevTools → Network → Response: cosa arriva davvero?
{ "user_name": "Carlo", ... }
  1. Codice client: cosa cerco?
utente.nome
  1. Documentazione/schema: cosa è stato concordato?
properties:
  user_name: { type: string }
  ...

Discrepanza: cliente vuole nome, contratto dice user_name. Bug nel client (o contratto da aggiornare, se la convenzione era camelCase).

Schema come fonte di verità

OpenAPI (REST)

paths:
  /utenti/{id}:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Utente'

components:
  schemas:
    Utente:
      type: object
      required: [id, user_name, is_active]
      properties:
        id: { type: integer }
        user_name: { type: string }
        user_age: { type: integer, minimum: 0 }
        is_active: { type: boolean }

Con OpenAPI:

  • Documenti il contratto in modo formale.
  • Generi client typed (TypeScript, Java, …) automaticamente.
  • Validi richieste e risposte in CI.

JSON Schema (validazione runtime)

Libreria ajv in JS:

import Ajv from "ajv";
const ajv = new Ajv();

const schema = {
  type: "object",
  required: ["user_name", "is_active"],
  properties: {
    user_name: { type: "string" },
    user_age: { type: "integer", minimum: 0 },
    is_active: { type: "boolean" },
  },
};

const valida = ajv.compile(schema);
if (!valida(dato)) console.error(valida.errors);

Aggiungi validazione al confine: ogni dato che entra dall’esterno passa il vaglio.

GraphQL SDL

In GraphQL il contratto è lo schema. Il client introspeziona, il server lo serve. Vantaggi: nessuna ambiguità. Svantaggi: complessità di setup.

Versioning

Quando il contratto deve cambiare in modo incompatibile:

  • URL versioning: /v1/utenti, /v2/utenti.
  • Header versioning: Accept-Version: 2.
  • Field versioning: nuovo campo opzionale, vecchio mantenuto.

Regola: i breaking change vanno in nuova versione. I non-breaking (aggiunte) possono restare in versione corrente.

Bug ricorrenti del contratto

  1. camelCase vs snake_case: vedi sopra.
  2. Date come stringhe diverse: "2025-01-15", "2025-01-15T00:00:00Z", "15/01/2025".
  3. ID come numero vs stringa: 42 vs "42". === non funziona.
  4. Campi opzionali assenti vs null: { "email": null } vs {} — JSON Schema può distinguere.
  5. Array vs oggetto singolo: { "items": [...] } vs { "items": {...} } quando uno solo.
  6. Enum estesi: server aggiunge un valore nuovo, client non lo gestisce.

Procedura di diagnosi

Davanti a un dato “strano”:

  1. Cattura la response in Network.
  2. Apri lo schema documentato (Swagger UI, README).
  3. Confronta:
    • I nomi dei campi corrispondono?
    • I tipi corrispondono?
    • Tutti i required sono presenti?
    • Ci sono campi non documentati?
  4. Annota la discrepanza nel taccuino.
  5. Fix: lato client se il contratto è rispettato dal server; lato server se invece il contratto è violato; entrambi se il contratto stesso era ambiguo.

Esercizio guidato

Prendi un’API pubblica (es. GitHub API, OpenWeatherMap, JSONPlaceholder):

  1. Fai una chiamata e cattura la response.
  2. Cerca lo schema/documentazione.
  3. Scrivi un piccolo JSON Schema per la response. Convalida con ajv.
  4. Simula un cambio del server (modifica manualmente il JSON ricevuto): rinomina un campo. Vedi come ajv reagisce.
  5. Aggiungi nel tuo codice client la validazione al confine: se non valida, log e fallback.

Errori frequenti del principiante

  • “Tanto funziona, il contratto lo sapremo dopo”. Sapremo dopo significa “scopriremo in produzione”.
  • Validare solo nel tipo TypeScript. I tipi TS sono compile-time, non runtime. Per dati esterni serve validazione runtime.
  • Ignorare campi inattesi. A volte il server aggiunge campi: ok, ma loggali per non perderli di vista.
  • Modificare schema senza versionare. Rompi i client esistenti.