🚀 Nuova versione beta disponibile! Feedback o problemi? Contattaci

Subscriptions in GraphQL: Implementazione con WebSocket

Codegrind Team•Sep 04 2024

Le subscriptions in GraphQL permettono ai client di ricevere aggiornamenti in tempo reale quando si verificano modifiche ai dati. A differenza delle query e delle mutazioni, che sono operazioni una tantum, le subscriptions mantengono una connessione attiva tra client e server, inviando aggiornamenti ogni volta che si verificano eventi specifici. Le subscriptions sono spesso implementate utilizzando WebSocket, un protocollo che consente la comunicazione bidirezionale continua. In questo articolo esploreremo come implementare le subscriptions in GraphQL utilizzando WebSocket, con esempi pratici e best practices per una gestione efficiente delle connessioni in tempo reale.

Cos’è una Subscription in GraphQL?

Una subscription è una funzionalità di GraphQL che consente di monitorare le modifiche ai dati e ricevere notifiche in tempo reale. Ad esempio, puoi utilizzare una subscription per ricevere notifiche quando viene aggiunto un nuovo messaggio a una chat, o quando una risorsa viene aggiornata.

Esempio di Schema Subscription

type Subscription {
  messageAdded: Message
}

type Message {
  id: ID!
  content: String!
  author: String!
}

type Query {
  messages: [Message!]!
}

In questo schema:

  • La subscription messageAdded permette ai client di ricevere notifiche in tempo reale ogni volta che viene aggiunto un nuovo messaggio.
  • Il tipo Message rappresenta il messaggio che viene notificato al client.

Implementazione delle Subscriptions con WebSocket

Per implementare le subscriptions in GraphQL utilizzando WebSocket, dobbiamo configurare un server GraphQL in grado di gestire le connessioni WebSocket. La combinazione di Apollo Server e GraphQL Subscriptions è una delle soluzioni più comuni.

1. Installare le Dipendenze Necessarie

Per configurare le subscriptions con WebSocket, dobbiamo installare alcune librerie:

npm install apollo-server graphql-subscriptions ws
  • apollo-server: Server GraphQL che supporta query, mutazioni e subscriptions.
  • graphql-subscriptions: Libreria che gestisce la pubblicazione e la sottoscrizione di eventi.
  • ws: Libreria che fornisce supporto WebSocket per le subscriptions.

2. Configurare il Server Apollo con WebSocket

Il passo successivo è configurare Apollo Server per supportare le subscriptions utilizzando WebSocket.

Esempio Completo di Server Apollo

const { ApolloServer, gql } = require("apollo-server");
const { PubSub } = require("graphql-subscriptions");
const { createServer } = require("http");
const { WebSocketServer } = require("ws");
const { useServer } = require("graphql-ws/lib/use/ws");

// Creiamo un'istanza di PubSub per gestire la pubblicazione degli eventi
const pubsub = new PubSub();

// Definizione dello schema
const typeDefs = gql`
  type Message {
    id: ID!
    content: String!
    author: String!
  }

  type Query {
    messages: [Message!]!
  }

  type Mutation {
    addMessage(content: String!, author: String!): Message
  }

  type Subscription {
    messageAdded: Message
  }
`;

// Dati di esempio
let messages = [];

// Risolutori per gestire query, mutazioni e subscriptions
const resolvers = {
  Query: {
    messages: () => messages,
  },
  Mutation: {
    addMessage: (parent, { content, author }) => {
      const message = { id: messages.length + 1, content, author };
      messages.push(message);
      pubsub.publish("MESSAGE_ADDED", { messageAdded: message }); // Pubblica l'evento
      return message;
    },
  },
  Subscription: {
    messageAdded: {
      subscribe: () => pubsub.asyncIterator("MESSAGE_ADDED"), // Sottoscrizione all'evento
    },
  },
};

// Creazione del server Apollo
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// Creazione di un server HTTP
const httpServer = createServer();

// Configurazione del WebSocket Server per le subscriptions
const wsServer = new WebSocketServer({
  server: httpServer,
  path: "/graphql",
});

useServer({ schema: server.schema }, wsServer);

// Avvio del server
server.start().then(() => {
  httpServer.listen(4000, () => {
    console.log(`Server ready at http://localhost:4000`);
  });
});

Cosa fa questo codice:

  • PubSub: Viene utilizzato per pubblicare e ascoltare eventi tra i client connessi.
  • messageAdded Subscription: Invia notifiche in tempo reale quando un nuovo messaggio viene aggiunto.
  • WebSocket Server: Gestisce la comunicazione bidirezionale continua tra client e server per le subscriptions.

3. Testare le Subscriptions

Per testare le subscriptions, puoi usare GraphQL Playground, che supporta le WebSocket per le subscriptions in modo nativo. Puoi inviare la seguente subscription:

subscription {
  messageAdded {
    id
    content
    author
  }
}

Ogni volta che un nuovo messaggio viene aggiunto tramite la seguente mutation:

mutation {
  addMessage(content: "Hello World", author: "Alice") {
    id
    content
    author
  }
}

Il client riceverà una notifica in tempo reale con i dettagli del nuovo messaggio.

Esempio di Risultato della Subscription

{
  "data": {
    "messageAdded": {
      "id": "1",
      "content": "Hello World",
      "author": "Alice"
    }
  }
}

4. Migliorare la Scalabilità delle Subscriptions

Quando implementi subscriptions con WebSocket in un ambiente di produzione, potresti incontrare problemi di scalabilità a causa del numero elevato di connessioni WebSocket aperte. Ecco alcune tecniche per migliorare la scalabilità:

a. Redis Pub/Sub per la Scalabilità

Se hai un’applicazione distribuita su più server, puoi utilizzare un sistema di messaggistica come Redis per coordinare gli eventi tra i server e distribuire le subscriptions in modo efficiente.

Esempio con Redis Pub/Sub

npm install ioredis
const Redis = require("ioredis");
const redis = new Redis();

const pubsub = new RedisPubSub({
  publisher: redis,
  subscriber: redis,
});

In questo esempio:

  • Redis viene utilizzato come sistema di messaggistica per pubblicare e distribuire eventi tra più istanze del server.

b. Bilanciamento del Carico con Sticky Sessions

Le WebSocket mantengono connessioni persistenti, quindi per gestire efficacemente il bilanciamento del carico tra più server, è necessario configurare il bilanciamento con sticky sessions. Questo garantisce che ogni client mantenga la stessa connessione con un server specifico durante la durata della sessione.

Un esempio di configurazione con NGINX:

http {
  upstream websocket_backend {
    server server1.example.com;
    server server2.example.com;
    sticky;
  }

  server {
    listen 80;

    location /graphql {
      proxy_pass http://websocket_backend;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_http_version 1.1;
      proxy_set_header X-Real-IP $remote_addr;
    }
  }
}

c. Timeout per Connessioni Inattive

Per evitare che le connessioni WebSocket rimangano aperte indefinitamente, è importante implementare un timeout per chiudere automaticamente le connessioni inattive. Questo può essere fatto gestendo il ciclo di vita delle connessioni all’interno del server.

wsServer.on("connection", (socket) => {
  socket.on("close", () => {
    console.log("Connection closed");
  });

  setTimeout(() => {
    socket.close();
  }, 60000); // Chiudi la connessione dopo 1 minuto di inattività
});

Best Practices per le Subscriptions con WebSocket

  1. Gestione delle Connessioni Inattive: Implementa un timeout per chiudere le connessioni inattive e ridurre il carico sul server.
  2. Bilanciamento del Carico: Utilizza sticky sessions con bilanciatori di carico come NGINX per mantenere le connessioni WebSocket attive su un singolo server.
  3. Scalabilità: Utilizza Redis Pub/Sub o un altro sistema di messaggistica distribuita per coordinare le subscriptions tra più server.
  4. Sicurezza: Implementa l’autenticazione tramite JWT o altri metodi per garantire che solo gli utenti autorizzati possano sottoscriversi e ricevere aggiornamenti.

Conclusione

Le subscriptions in GraphQL, implementate con WebSocket, sono una potente funz

ionalità per fornire aggiornamenti in tempo reale ai client. Configurare correttamente le subscriptions richiede la gestione di connessioni WebSocket, l’uso di sistemi di pubblicazione/sottoscrizione come Redis per la scalabilità e l’implementazione di strategie per gestire un numero elevato di connessioni. Seguendo queste best practices, puoi creare applicazioni reattive e scalabili con aggiornamenti in tempo reale in modo efficiente e sicuro.