GraphQL path · At scale · Topic 10 of 12

Federation: one graph from many services

Not started

Federation is a way to build one GraphQL API out of several independently deployed GraphQL services. Each **subgraph** owns part of the schema and the resolvers behind it. A **composition** step merges the subgraph schemas into one **supergraph** and checks that they fit together. A **router** (or gateway) receives client queries against that supergraph, works out which subgraph can resolve which fields, sends each its part, and stitches the answers into one response. Clients see a single endpoint and a single schema; teams keep their own code, deploys and data. Apollo Federation popularised the approach, and a GraphQL Foundation working group on composite schemas is writing a vendor-neutral specification for it.

Why it matters: One schema for the whole company is what clients want; one server owned by every team is what nobody wants. Federation keeps the single graph while letting each team ship its part on its own schedule.

  1. 1Subgraphs

    Each team runs a GraphQL service for its own domain, with its own schema and resolvers: users, orders, products.

  2. 2Entities join the graph

    A type with a key (such as `User` by `id`) can be referenced and extended by other subgraphs. Orders can add `User.orders` without owning `User`.

  3. 3Composition at build time

    Subgraph schemas are composed into one supergraph before deployment, and conflicts fail the composition, not production traffic.

  4. 4A router plans each query

    The router splits a client query into subgraph requests, runs them in dependency order (batched by entity), and merges the results.

1

How it works

The explanation, with examples you can run.

Entities

The glue is the entity: an object type with a key that any subgraph can use to refer to it. In Apollo Federation's syntax:

# Users subgraph: owns Usertype User @key(fields: "id") {  id: ID!  name: String!}type Query { user(id: ID!): User }
# Orders subgraph: owns Order, and adds a field to Usertype User @key(fields: "id") {  id: ID!  orders(first: Int = 10): [Order!]!}type Order @key(fields: "id") {  id: ID!  total: Int!  product: Product!}type Product @key(fields: "id") { id: ID! }   # a reference: Products owns the rest

The client sees one User type with name and orders, and one Order whose product has every field the Products subgraph defines.

What the router does

query { user(id: "u1") { name orders { total product { title } } } }
  1. Ask Users for user(id: "u1") { id name }.
  2. Send Orders the entity key { __typename: "User", id: "u1" } and ask for orders { total product { id } }.
  3. Send Products all the product keys from step 2 in one request and ask for title.
  4. Merge the three results into the shape of the original query.

This plan is the execution model from earlier in this path, applied across services: parents before children, siblings in parallel, and entity lookups batched per step so ten orders don't mean ten calls to Products. Inside each subgraph, a DataLoader still turns those keys into one database query.

Costs and trade-offs

  • Latency adds up along the plan's longest chain of dependent fetches. Deeply interleaved ownership (A needs B needs A) makes slow plans.
  • The router is critical infrastructure: authentication, cost limits, timeouts and tracing usually live there, as in any API gateway.
  • Composition is a contract check. Run it in CI for every subgraph change, together with checks of the operations clients actually send, so a removed field is caught before deploy.
  • You may not need it. A single GraphQL server in front of several REST or gRPC services (a backend for frontend) is simpler until several teams need to evolve the schema independently.
2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.The Orders team wants clients to query `user { orders { total } }`, but `User` belongs to the Users subgraph. What do they do?
  2. 2.When should a subgraph's breaking schema change be caught?
  3. 3.A query for 50 orders and each order's products crosses two subgraphs. What keeps it from becoming 50 calls to the products subgraph?

0 of 3 right so far. Get all three to finish this topic.