La federación de GraphQL es una técnica avanzada que permite dividir un esquema de GraphQL en múltiples servicios independientes, cada uno responsable de una parte del esquema. Esto facilita la escalabilidad y la modularidad en aplicaciones grandes y complejas.

¿Qué es la Federación de GraphQL?

La federación de GraphQL permite que múltiples servicios colaboren para resolver una única consulta GraphQL. En lugar de tener un único servidor monolítico que maneje todas las resoluciones, la federación permite que diferentes servicios manejen diferentes partes del esquema.

Beneficios de la Federación de GraphQL

  • Escalabilidad: Permite escalar diferentes partes del esquema de manera independiente.
  • Modularidad: Facilita la división del código en módulos más manejables.
  • Desarrollo Independiente: Equipos diferentes pueden trabajar en diferentes servicios sin interferir entre sí.
  • Despliegue Independiente: Permite desplegar servicios de manera independiente, reduciendo el riesgo de errores en el despliegue.

Conceptos Clave

Gateway

El Gateway es el punto de entrada único para todas las consultas GraphQL. Se encarga de delegar las resoluciones a los servicios correspondientes.

Servicios Federados

Son los servicios individuales que manejan partes específicas del esquema. Cada servicio define su propio esquema y resolvers.

Extensiones de Esquema

Permiten que un servicio extienda tipos definidos en otro servicio. Esto es crucial para la colaboración entre servicios.

Configuración de un Gateway Federado

Paso 1: Configuración del Gateway

Primero, configuramos el Gateway utilizando Apollo Server.

const { ApolloServer } = require('apollo-server');
const { ApolloGateway } = require('@apollo/gateway');

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'users', url: 'http://localhost:4001/graphql' },
    { name: 'products', url: 'http://localhost:4002/graphql' },
  ],
});

const server = new ApolloServer({ gateway });

server.listen().then(({ url }) => {
  console.log(`🚀 Gateway ready at ${url}`);
});

Paso 2: Configuración de Servicios Federados

Cada servicio federado debe definir su propio esquema y resolvers. Aquí hay un ejemplo de un servicio de usuarios.

Esquema del Servicio de Usuarios

# users/schema.graphql
type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
}

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

Resolvers del Servicio de Usuarios

// users/index.js
const { ApolloServer, gql } = require('apollo-server');
const { buildFederatedSchema } = require('@apollo/federation');

const typeDefs = gql`
  type User @key(fields: "id") {
    id: ID!
    name: String!
    email: String!
  }

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

const resolvers = {
  Query: {
    user(_, { id }) {
      return users.find(user => user.id === id);
    },
  },
  User: {
    __resolveReference(user) {
      return users.find(u => u.id === user.id);
    },
  },
};

const server = new ApolloServer({
  schema: buildFederatedSchema([{ typeDefs, resolvers }]),
});

server.listen({ port: 4001 }).then(({ url }) => {
  console.log(`🚀 Users service ready at ${url}`);
});

Paso 3: Extender Tipos en Otros Servicios

Un servicio puede extender tipos definidos en otro servicio. Aquí hay un ejemplo de un servicio de productos que extiende el tipo User.

Esquema del Servicio de Productos

# products/schema.graphql
extend type User @key(fields: "id") {
  id: ID! @external
  products: [Product]
}

type Product {
  id: ID!
  name: String!
  price: Float!
}

extend type Query {
  product(id: ID!): Product
}

Resolvers del Servicio de Productos

// products/index.js
const { ApolloServer, gql } = require('apollo-server');
const { buildFederatedSchema } = require('@apollo/federation');

const typeDefs = gql`
  extend type User @key(fields: "id") {
    id: ID! @external
    products: [Product]
  }

  type Product {
    id: ID!
    name: String!
    price: Float!
  }

  extend type Query {
    product(id: ID!): Product
  }
`;

const resolvers = {
  User: {
    products(user) {
      return products.filter(product => product.userId === user.id);
    },
  },
  Query: {
    product(_, { id }) {
      return products.find(product => product.id === id);
    },
  },
};

const server = new ApolloServer({
  schema: buildFederatedSchema([{ typeDefs, resolvers }]),
});

server.listen({ port: 4002 }).then(({ url }) => {
  console.log(`🚀 Products service ready at ${url}`);
});

Ejercicio Práctico

Ejercicio

  1. Configura un Gateway federado que incluya dos servicios: orders y customers.
  2. Define un esquema y resolvers para cada servicio.
  3. Asegúrate de que el servicio orders pueda extender el tipo Customer definido en el servicio customers.

Solución

Esquema del Servicio de Clientes

# customers/schema.graphql
type Customer @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
}

extend type Query {
  customer(id: ID!): Customer
}

Resolvers del Servicio de Clientes

// customers/index.js
const { ApolloServer, gql } = require('apollo-server');
const { buildFederatedSchema } = require('@apollo/federation');

const typeDefs = gql`
  type Customer @key(fields: "id") {
    id: ID!
    name: String!
    email: String!
  }

  extend type Query {
    customer(id: ID!): Customer
  }
`;

const resolvers = {
  Query: {
    customer(_, { id }) {
      return customers.find(customer => customer.id === id);
    },
  },
  Customer: {
    __resolveReference(customer) {
      return customers.find(c => c.id === customer.id);
    },
  },
};

const server = new ApolloServer({
  schema: buildFederatedSchema([{ typeDefs, resolvers }]),
});

server.listen({ port: 4003 }).then(({ url }) => {
  console.log(`🚀 Customers service ready at ${url}`);
});

Esquema del Servicio de Pedidos

# orders/schema.graphql
extend type Customer @key(fields: "id") {
  id: ID! @external
  orders: [Order]
}

type Order {
  id: ID!
  product: String!
  quantity: Int!
}

extend type Query {
  order(id: ID!): Order
}

Resolvers del Servicio de Pedidos

// orders/index.js
const { ApolloServer, gql } = require('apollo-server');
const { buildFederatedSchema } = require('@apollo/federation');

const typeDefs = gql`
  extend type Customer @key(fields: "id") {
    id: ID! @external
    orders: [Order]
  }

  type Order {
    id: ID!
    product: String!
    quantity: Int!
  }

  extend type Query {
    order(id: ID!): Order
  }
`;

const resolvers = {
  Customer: {
    orders(customer) {
      return orders.filter(order => order.customerId === customer.id);
    },
  },
  Query: {
    order(_, { id }) {
      return orders.find(order => order.id === id);
    },
  },
};

const server = new ApolloServer({
  schema: buildFederatedSchema([{ typeDefs, resolvers }]),
});

server.listen({ port: 4004 }).then(({ url }) => {
  console.log(`🚀 Orders service ready at ${url}`);
});

Configuración del Gateway

const { ApolloServer } = require('apollo-server');
const { ApolloGateway } = require('@apollo/gateway');

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'customers', url: 'http://localhost:4003/graphql' },
    { name: 'orders', url: 'http://localhost:4004/graphql' },
  ],
});

const server = new ApolloServer({ gateway });

server.listen().then(({ url }) => {
  console.log(`🚀 Gateway ready at ${url}`);
});

Conclusión

La federación de GraphQL es una poderosa técnica para manejar esquemas grandes y complejos de manera modular y escalable. Al dividir el esquema en múltiples servicios independientes, se facilita el desarrollo, despliegue y mantenimiento de aplicaciones grandes. Con la práctica y la correcta implementación, la federación puede mejorar significativamente la eficiencia y la colaboración en equipos de desarrollo.

© Copyright 2024. Todos los derechos reservados