🚀 Nuova versione beta disponibile! Feedback o problemi? Contattaci

Risolutori di Campo Personalizzati in GraphQL: Gestione Avanzata dei Dati

Codegrind Team•Sep 03 2024

In GraphQL, i risolutori di campo personalizzati ti permettono di controllare come i dati vengono restituiti per campi specifici. I risolutori sono funzioni associate ai campi definiti nello schema e possono essere utilizzati per eseguire logica personalizzata per ogni campo, come trasformare i dati, effettuare calcoli o recuperare dati da fonti esterne. In questo articolo esploreremo come creare e gestire risolutori di campo personalizzati in GraphQL, con esempi pratici su come utilizzarli per ottenere maggiore controllo e flessibilità nell’accesso ai dati.

Cos’è un Risolutore di Campo Personalizzato?

Un risolutore di campo personalizzato è una funzione che gestisce la logica per risolvere un campo specifico in un tipo GraphQL. In GraphQL, ogni campo può avere un risolutore che definisce come ottenere o calcolare il valore di quel campo. Se un campo non ha un risolutore esplicito, GraphQL utilizza un risolutore predefinito che restituisce il valore dell’oggetto genitore associato a quel campo.

Esempio di Schema senza Risolutore Personalizzato

In uno schema GraphQL di base, puoi avere un tipo User che restituisce campi come id, name e email:

type User {
  id: ID!
  name: String!
  email: String!
}

type Query {
  user(id: ID!): User
}

Se il campo user è risolto da un risolutore predefinito, GraphQL si aspetta che l’oggetto restituito dal risolutore contenga già i campi id, name, e email. Ma se hai bisogno di una logica più complessa, come la trasformazione dei dati o l’accesso a più fonti di dati, puoi definire risolutori di campo personalizzati.

Come Funzionano i Risolutori di Campo

Un risolutore di campo riceve quattro argomenti principali:

  1. parent: L’oggetto genitore restituito dal risolutore del livello superiore.
  2. args: Gli argomenti passati alla query o mutazione dal client.
  3. context: Un oggetto che può contenere dati condivisi tra tutti i risolutori, come le informazioni sull’utente o l’accesso al database.
  4. info: Informazioni sull’esecuzione della query, inclusi lo schema e i metadati.

Sintassi di Base di un Risolutore di Campo

const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      return context.db.getUserById(args.id);
    },
  },
  User: {
    name: (parent, args, context, info) => {
      return parent.name.toUpperCase();
    },
  },
};

In questo esempio, abbiamo definito un risolutore personalizzato per il campo name del tipo User, che restituisce il nome in lettere maiuscole. Il risolutore accede al campo name dall’oggetto genitore (l’utente) e applica una trasformazione prima di restituirlo.

Creare Risolutori di Campo Personalizzati

1. Trasformare i Dati in un Risolutore di Campo

I risolutori di campo personalizzati sono particolarmente utili quando hai bisogno di trasformare i dati prima di restituirli al client. Ad esempio, potresti voler formattare un campo come una data o un valore calcolato.

Esempio: Formattazione di una Data

const resolvers = {
  User: {
    createdAt: (parent) => {
      // Converte la data ISO in un formato leggibile
      return new Date(parent.createdAt).toLocaleDateString();
    },
  },
};

In questo esempio, il campo createdAt del tipo User viene formattato come una stringa leggibile, anziché restituire il valore ISO standard.

2. Calcolare un Campo Derivato

A volte, i campi che vuoi restituire non esistono direttamente nel database, ma possono essere calcolati in base ad altri campi. In questi casi, puoi utilizzare un risolutore personalizzato per eseguire il calcolo.

Esempio: Calcolo dell’Età

const resolvers = {
  User: {
    age: (parent) => {
      const birthDate = new Date(parent.birthDate);
      const ageDiff = Date.now() - birthDate.getTime();
      return Math.floor(ageDiff / (1000 * 60 * 60 * 24 * 365.25));
    },
  },
};

In questo esempio, il risolutore del campo age calcola l’età dell’utente in base alla sua data di nascita (birthDate), che esiste nell’oggetto genitore.

3. Accedere a Dati Correlati

I risolutori di campo personalizzati possono anche essere utilizzati per recuperare dati correlati. Ad esempio, se hai un campo author in un tipo Post e devi recuperare i dettagli dell’autore da un’altra tabella o API.

Esempio: Recupero dell’Autore di un Post

const resolvers = {
  Post: {
    author: async (parent, args, context) => {
      // Recupera i dettagli dell'autore dal database
      return await context.db.getUserById(parent.authorId);
    },
  },
};

In questo esempio, il campo author del tipo Post utilizza un risolutore personalizzato per recuperare i dettagli dell’autore utilizzando l’authorId presente nell’oggetto Post.

4. Gestire Campi Complessi con Risolutori Personalizzati

Quando i campi richiedono logiche complesse, come il recupero di dati da API esterne o l’esecuzione di operazioni multiple, i risolutori di campo personalizzati forniscono un controllo granulare sul modo in cui vengono gestiti i dati.

Esempio: Recupero di Dati da un’API Esterna

Supponiamo di avere un campo weather per una località e di voler recuperare le informazioni meteo da un’API esterna.

const fetch = require("node-fetch");

const resolvers = {
  Location: {
    weather: async (parent, args, context) => {
      const response = await fetch(
        `https://api.weather.com/v3/wx/conditions/current?geocode=${parent.lat},${parent.lng}&format=json&apiKey=YOUR_API_KEY`
      );
      const weatherData = await response.json();
      return weatherData;
    },
  },
};

In questo esempio, il campo weather del tipo Location esegue una chiamata a un’API meteo esterna utilizzando le coordinate di latitudine e longitudine (lat e lng) fornite dall’oggetto genitore.

Migliorare le Performance con Risolutori di Campo

Quando lavori con risolutori di campo personalizzati, è importante considerare l’impatto sulle prestazioni. Operazioni come il recupero di dati correlati o chiamate API multiple possono rallentare la risposta. Qui ci sono alcune tecniche per migliorare le prestazioni:

1. Batchare le Richieste con DataLoader

Se stai recuperando dati correlati come autori, utenti o commenti, potresti incorrere nel problema N+1 query. Questo problema si verifica quando GraphQL esegue una query separata per ogni campo correlato, aumentando il carico sul database.

DataLoader è uno strumento che consente di batchare e memorizzare in cache le richieste, riducendo il numero di query.

Esempio con DataLoader

const DataLoader = require("dataloader");

const userLoader = new DataLoader(async (userIds) => {
  const users = await db.getUsersByIds(userIds);
  return userIds.map((id) => users.find((user) => user.id === id));
});

const resolvers = {
  Post: {
    author: (parent) => userLoader.load(parent.authorId),
  },
};

In questo esempio, se più post richiedono gli stessi autori, DataLoader batcha le richieste in una sola query per migliorare l’efficienza.

2. Caching dei Risultati

Se il campo personalizzato richiede il recupero di dati da un’API esterna o un database, puoi implementare un sistema di caching per memorizzare i risultati e migliorare le prestazioni.

Esempio con Redis

const redis = require("redis");
const client = redis.createClient();

const resolvers = {
  Location: {
    weather: async (parent) => {
      const cacheKey = `weather:${parent.lat},${parent.lng}`;
      const cachedWeather = await client.get(cacheKey);

      if (cachedWeather) {
        return JSON.parse(cachedWeather);
      }

      const weatherData = await fetchWeatherAPI(parent.lat, parent.lng);
      client.set(cacheKey, JSON.stringify(weatherData), "EX", 3600); // Cache per 1 ora
      return weatherData;
    },
  },
};

Con questa soluzione, i risultati dell’API meteo vengono memorizzati in Redis per migliorare le prestazioni delle richieste future.

Conclusione

I risolutori di campo personalizzati in GraphQL offrono una flessibilitĂ  incredibile nella gestione dei dati, permettendo di trasformare, calcolare e recuperare dati in modo personalizzato. Che tu stia lavorando con dati complessi, chiamate API o calcoli derivati, i risolutori di campo ti permettono di avere il controllo completo su come i dati vengono gestiti e restituiti al client.

Utilizzando tecniche come il batching con DataLoader e il caching, puoi migliorare le prestazioni e garantire che la tua API GraphQL rimanga efficiente anche quando gestisce dati complessi e operazioni multiple.