GraphQL Federation is a powerful technique that allows you to compose multiple GraphQL services into a single, unified API. This approach is particularly useful in microservices architectures, where different teams or services manage different parts of the schema. Federation helps in scaling GraphQL across large organizations by enabling a modular and maintainable schema design.

Key Concepts

  1. Federated Services

Federated services are individual GraphQL services that expose part of the overall schema. Each service is responsible for a specific domain or functionality.

  1. Gateway

The gateway is a central service that aggregates the schemas from all federated services and provides a unified API to the clients. It handles query planning and execution across the federated services.

  1. Schema Composition

Schema composition is the process of merging schemas from multiple federated services into a single schema. This involves resolving conflicts and ensuring that the combined schema is valid.

  1. Type Extensions

Type extensions allow federated services to extend types defined in other services. This is useful for adding fields or relationships that span multiple services.

  1. Entities and Keys

Entities are types that can be referenced across services. Keys are fields that uniquely identify an entity, enabling services to resolve references to entities managed by other services.

Setting Up GraphQL Federation

Step 1: Define Federated Services

Each federated service should define its own schema and expose it using a GraphQL server. Here's an example of a simple federated service:

User Service Schema (user-service/schema.graphql)

type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
}

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

User Service Resolver (user-service/resolvers.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!
  }

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

const resolvers = {
  Query: {
    user: (_, { id }) => {
      // Fetch user by ID from the database
      return { id, name: "John Doe", email: "[email protected]" };
    },
  },
  User: {
    __resolveReference(user) {
      // Resolve user reference
      return { id: user.id, name: "John Doe", email: "[email protected]" };
    },
  },
};

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

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

Step 2: Set Up the Gateway

The gateway aggregates the schemas from all federated services and provides a unified API.

Gateway Setup (gateway/index.js)

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

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'user', url: 'http://localhost:4001' },
    // Add other federated services here
  ],
});

const server = new ApolloServer({
  gateway,
  subscriptions: false,
});

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

Step 3: Extend Types Across Services

Federated services can extend types defined in other services. For example, a Post service can extend the User type to include posts authored by the user.

Post Service Schema (post-service/schema.graphql)

extend type User @key(fields: "id") {
  id: ID! @external
  posts: [Post]
}

type Post {
  id: ID!
  title: String!
  content: String!
  authorId: ID!
}

type Query {
  postsByUser(userId: ID!): [Post]
}

Post Service Resolver (post-service/resolvers.js)

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

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

  type Post {
    id: ID!
    title: String!
    content: String!
    authorId: ID!
  }

  type Query {
    postsByUser(userId: ID!): [Post]
  }
`;

const resolvers = {
  User: {
    posts(user) {
      // Fetch posts by user ID from the database
      return [
        { id: "1", title: "Post 1", content: "Content 1", authorId: user.id },
        { id: "2", title: "Post 2", content: "Content 2", authorId: user.id },
      ];
    },
  },
  Query: {
    postsByUser: (_, { userId }) => {
      // Fetch posts by user ID from the database
      return [
        { id: "1", title: "Post 1", content: "Content 1", authorId: userId },
        { id: "2", title: "Post 2", content: "Content 2", authorId: userId },
      ];
    },
  },
};

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

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

Step 4: Query the Unified API

Clients can now query the unified API provided by the gateway, which will delegate the queries to the appropriate federated services.

Example Query

query {
  user(id: "1") {
    id
    name
    email
    posts {
      id
      title
      content
    }
  }
}

Practical Exercise

Exercise: Implement a Federated Service

  1. Create a Product Service

    • Define a Product type with fields id, name, and price.
    • Implement a query to fetch a product by ID.
  2. Extend the User Type

    • Extend the User type in the Product service to include a list of products purchased by the user.
  3. Set Up the Gateway

    • Add the Product service to the gateway's service list.
  4. Query the Unified API

    • Write a query to fetch a user along with the products they have purchased.

Solution

Product Service Schema (product-service/schema.graphql)

extend type User @key(fields: "id") {
  id: ID! @external
  purchasedProducts: [Product]
}

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

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

Product Service Resolver (product-service/resolvers.js)

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

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

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

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

const resolvers = {
  User: {
    purchasedProducts(user) {
      // Fetch products purchased by user ID from the database
      return [
        { id: "1", name: "Product 1", price: 100.0 },
        { id: "2", name: "Product 2", price: 200.0 },
      ];
    },
  },
  Query: {
    product: (_, { id }) => {
      // Fetch product by ID from the database
      return { id, name: "Product 1", price: 100.0 };
    },
  },
};

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

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

Gateway Setup (gateway/index.js)

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

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'user', url: 'http://localhost:4001' },
    { name: 'post', url: 'http://localhost:4002' },
    { name: 'product', url: 'http://localhost:4003' },
  ],
});

const server = new ApolloServer({
  gateway,
  subscriptions: false,
});

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

Example Query

query {
  user(id: "1") {
    id
    name
    email
    purchasedProducts {
      id
      name
      price
    }
  }
}

Conclusion

GraphQL Federation is a robust solution for scaling GraphQL across large organizations and microservices architectures. By composing multiple GraphQL services into a single, unified API, you can achieve a modular and maintainable schema design. This module covered the key concepts of federation, setting up federated services, extending types across services, and querying the unified API. With practical examples and exercises, you should now be equipped to implement GraphQL Federation in your own projects.

© Copyright 2024. All rights reserved