GraphQL for Microservices: Schema Stitching vs. Federation In Depth
AI generated
{ }
type
GraphQL · Microservices · Federation · Architecture
GraphQL for Microservices
Schema Stitching Against Apollo Federation In Detail

Anyone wanting to unify multiple microservices under a single GraphQL endpoint faces a choice between manual schema stitching and declarative federation. Both approaches solve the shared schema problem in fundamentally different ways, with noticeable consequences for team autonomy, performance and operational overhead.

19 min read Schema Stitching · Apollo Federation · Subgraphs · Gateway GraphQL 16 · Apollo Federation 2 · Microservices

1. Why microservices need a shared GraphQL schema

As soon as a system is decomposed into multiple microservices, a contradiction emerges that GraphQL for microservices has to resolve: frontends want to query a single, consistent graph, while backends are deliberately kept separate and independently deployable. One team manages product data, another orders, a third customer data. Without a unifying layer, clients would have to work against three separate endpoints and stitch the data together themselves. That exact stitching problem is what a federated GraphQL schema is meant to solve centrally and type safely.

In practice the answer to this requirement is usually schema stitching or federation, two fundamentally different ways of merging several partial schemas into one overall schema. Both approaches within GraphQL for microservices pursue the same goal, a single query interface on the outside, distributed responsibilities on the inside, but differ sharply in implementation, in how tightly the gateway couples to the services, and in how much governance a team has to build around it. The following sections walk through both approaches in detail, with concrete examples from a production distributed architecture.

2. Schema stitching: how it works and its limits

Schema stitching was historically the first practical way to combine multiple GraphQL schemas. A central gateway process fetches the schemas of all participating services via introspection or from static SDL files, merges them into a single executable schema, and defines manual delegation rules at the seams. These rules determine which sub service is actually queried for which field, and how results from multiple services are merged into a single object. The gateway carries the full responsibility for this linking logic.

The decisive drawback of schema stitching shows up when scaling: every new link between two services has to be explicitly coded in the gateway, typically via so called type merging config or remote schema transforms. With three or four services this is manageable, with twenty services the gateway itself becomes a monolith that has to be redeployed on every schema change from any team. That exact coupling problem was the starting point for Apollo Federation, which solves the same task declaratively instead of imperatively.


# Schema Stitching: manual delegation config (conceptual, graphql-tools style)
# Subservice A: products
type Product {
  id: ID!
  sku: String!
  name: String!
}

# Subservice B: reviews
type Review {
  id: ID!
  productId: ID!
  rating: Int!
  text: String!
}

# Gateway must manually define how "reviews" hangs off "Product" -
# this merge logic lives OUTSIDE both schemas, in gateway config
extend type Product {
  reviews: [Review!]!
}

3. Apollo Federation: subgraphs, entities and the supergraph

Apollo Federation flips the principle of schema stitching around: instead of a central gateway owning the linking logic, each subgraph declares itself which fields it contributes to a shared type. The @key directive marks a field as the unique identifier of an entity that may be distributed across several subgraphs. The gateway, called the router in Apollo terminology, composes a so called supergraph from these at build time, a single schema document describing which subgraph supplies which field.

The big advantage for GraphQL for microservices with federation: teams can deploy their subgraphs independently without touching the gateway. A new field on Product in the product team subgraph automatically appears in the supergraph as soon as composition runs again, usually automated in the CI pipeline via the Rover tooling. This decoupling is the main reason federation has won out over classic schema stitching in large organizations with many autonomous teams.


# Federation: Products subgraph declares the entity and its key
type Product @key(fields: "id") {
  id: ID!
  sku: String!
  name: String!
  price: Money!
}

# Federation: Reviews subgraph extends the SAME entity independently -
# no coordination with the Products team required at deploy time
type Product @key(fields: "id") {
  id: ID! @external
  reviews: [Review!]!
  averageRating: Float!
}

type Review {
  id: ID!
  rating: Int!
  text: String!
  author: String!
}

4. Entity resolution and reference resolvers in detail

The heart of every federation implementation is the reference resolver, called __resolveReference in Apollo terminology. When the router processes a query that combines fields from multiple subgraphs for the same entity, it first sends a query to the subgraph that originally supplies the entity, then an _entities query to every additional subgraph contributing extra fields. This second call only passes the key, usually the ID, and the subgraph must reconstruct the complete object for its own portion from that alone.

This is exactly where most performance problems in GraphQL for microservices arise in practice: a naive reference resolver that fires a single database query per entity produces a hundred individual requests to the reviews service for a list of a hundred products. The fix is the same DataLoader approach used for normal N plus one handling, except the batching point here is the _entities resolver, which collects all requested keys in one call and forwards them as a single batch query to the database.


// Reviews subgraph: __resolveReference with batched DataLoader
// (webonyx/graphql-php style, simplified for clarity)
final class ProductEntityResolver
{
    public function __construct(
        private readonly ReviewBatchLoader $batchLoader,
    ) {
    }

    /**
     * Resolves the "reviews" and "averageRating" fields for a Product
     * entity reference coming from the federation gateway.
     *
     * @param array{id: string} $reference Entity reference with the @key fields
     * @return array{id: string, reviews: array, averageRating: float}
     */
    public function resolveReference(array $reference): array
    {
        // The batch loader collects all reference IDs from one gateway
        // round-trip and issues a single SQL query, not one per product.
        $reviews = $this->batchLoader->load($reference['id']);

        return [
            'id' => $reference['id'],
            'reviews' => $reviews,
            'averageRating' => $this->average($reviews),
        ];
    }

    private function average(array $reviews): float
    {
        if ($reviews === []) {
            return 0.0;
        }

        $sum = array_sum(array_column($reviews, 'rating'));

        return round($sum / count($reviews), 2);
    }
}

5. Schema ownership and team boundaries in federated systems

Federation forces a decision that a monolithic schema never has to make: who owns which field of a shared type? The product team owns Product.name and Product.price, the reviews team owns Product.reviews and Product.averageRating. This split should follow domain bounded contexts, not technical coincidence, or subgraphs end up cut for organizational convenience instead of domain coherence.

In practice, GraphQL for microservices benefits from a written schema ownership matrix that assigns every type and every contested field to a team, combined with CODEOWNERS files in each subgraph repository. Conflicts typically arise around fields that concern several teams equally, such as a status field that affects both order and shipping logic. Here the federation directive @shareable helps, explicitly allowing multiple subgraphs to supply the same field as long as they return consistent values.

6. Query planning and performance in the federation gateway

Before the router answers an incoming query, it builds a query plan, a sequence of partial requests to the participating subgraphs, optimized for minimal round trips. Fields originating from the same subgraph are combined into a single request, fields from different subgraphs are requested in parallel rather than sequentially wherever the dependency structure allows. This plan can be printed as a JSON structure by the Apollo Router in debug mode and immediately shows where unexpected sequential calls occur.

A common performance problem in GraphQL for microservices is deep nesting across multiple subgraphs: a query fetching products, their reviews, and the authors of those reviews from three different services produces a query plan with three sequential stages, because each stage needs the keys from the previous one. Response caching at the subgraph level and aggressive batch sizes in the router noticeably reduce this latency, but do nothing to change the underlying stage count dictated by the schema structure.


# Rover CLI: compose a supergraph from multiple subgraph schemas
# and publish it to the schema registry
rover subgraph check my-graph@production \
  --schema ./products/schema.graphql \
  --name products

rover supergraph compose \
  --config ./supergraph-config.yaml \
  --output ./supergraph.graphql

rover subgraph publish my-graph@production \
  --schema ./products/schema.graphql \
  --name products \
  --routing-url https://products.internal/graphql

7. Versioning and schema evolution across team boundaries

A federated schema is in constant flux because independent teams deploy independently. That is exactly why schema evolution in GraphQL for microservices is trickier than in a single service: a breaking change in one subgraph can hit clients that access fields owned by an entirely different team through the gateway, without the offending team ever knowing. Composition checks in the CI pipeline, validating every proposed subgraph change against actual query traffic patterns, are not a nice to have here but a baseline requirement for safe, independent deployment.

The schema registry concept, as offered by Apollo GraphOS or alternatives such as Hive, stores not only the current version of every subgraph but also which fields clients actually use. A field flagged as a breaking change that, according to traffic analysis, nobody has queried in months can be removed without blocking the composition check. This data basis prevents teams from dragging dead fields along for years out of sheer caution.

8. Monitoring and debugging federated GraphQL systems

A single failing request in GraphQL for microservices can travel through three, four or five subgraphs before the router assembles a response. Without end to end distributed tracing that propagates a trace ID from the incoming request through every subgraph call, debugging a federated architecture is practically hopeless. The Apollo Router supports OpenTelemetry natively and exports spans for every phase, parsing, validation, query planning, and every individual subgraph request.

A second, often overlooked aspect is error propagation: if one subgraph fails while other parts of the query were answered successfully, GraphQL, per spec, returns a partial response with an errors array and null at the affected spot. Clients must be prepared to render partial data correctly instead of discarding the entire response, or a single subgraph outage will make the whole system look down even though ninety percent of the data arrived valid.

9. Schema stitching vs. federation head to head

Both approaches solve the same underlying task but differ substantially in coupling, operational overhead and team autonomy. The table below summarizes the key decision criteria for GraphQL for microservices.

Criterion Schema Stitching Apollo Federation
Linking logic Central in the gateway, manually coded Decentralized per subgraph, declarative via @key
Team autonomy Low, gateway team becomes the bottleneck High, independent deploys per subgraph
Scaling to many services Gateway itself becomes a monolith Linear, composition automated
Entry barrier Low, no new directive syntax Higher, must learn @key/@shareable/@external
Tooling ecosystem Small, mostly graphql-tools Large, Rover, GraphOS, Router, Hive

For small systems with two or three services and a single gateway team, schema stitching remains a pragmatic, quickly implementable path. But as soon as several autonomous teams want to deploy independently, which is the actual purpose of a microservices architecture in the first place, the structural advantages of federation clearly outweigh the steeper initial learning curve.

Mironsoft

GraphQL architecture, microservices and Magento integration

A shared GraphQL schema for your microservices?

We analyze your existing service landscape, design a subgraph split along domain boundaries, and support the introduction of Apollo Federation from the first entity to a production router.

Architecture review

Schema ownership matrix and subgraph split along domain bounded contexts

Federation rollout

Migrating from schema stitching or REST to Apollo Federation with router setup

Performance tuning

DataLoader batching in reference resolvers and query plan optimization

10. Summary

GraphQL for microservices always needs a federation strategy once more than one team works on a shared schema. Schema stitching solves the problem centrally and manually, with a gateway that itself becomes a bottleneck as the service count grows. Apollo Federation moves the linking logic out into each subgraph, via the @key directive and reference resolvers, and thereby enables real team autonomy with independent deployments.

The switch pays off above all once the number of participating teams grows and the coupling through a central gateway becomes a noticeable drag. Anyone starting fresh who will foreseeably need to federate more than three or four services should start with federation directly, because retrofitting federation onto an existing schema stitching setup means, in practice, a complete reimplementation of the linking logic.

GraphQL for Microservices — The Essentials at a Glance

Schema Stitching

Central gateway with manual delegation logic. Practical for two to three services, quickly becomes a monolith itself with more services.

Apollo Federation

Decentralized linking via @key entities. Each subgraph supplies its own fields, the router automatically composes the supergraph.

Reference resolver

__resolveReference must be batched, or N plus one occurs at the entity level for every list spanning multiple subgraphs.

Schema ownership

A clear field to team mapping and composition checks in the CI pipeline are mandatory, not optional.

11. FAQ: GraphQL for Microservices

1Fundamental difference stitching vs. federation?
Stitching manages linking centrally in the gateway, federation decentrally in each subgraph via @key. Federation allows independent deploys per team.
2When does federation pay off?
From four to five independent teams with their own deploy cycles. With two to three services, schema stitching is often enough.
3What does @key do?
Marks the unique identification field of an entity. Multiple subgraphs can independently extend the type with the same @key.
4Avoiding N plus one in reference resolvers?
The router batches all references in one _entities call. The subgraph must process it with a DataLoader instead of individual queries.
5What happens on subgraph failure?
A partial response with a populated errors array and null at the affected spot. Other fields are answered normally.
6Field allowed in multiple subgraphs?
Yes, with @shareable. Without this directive, composition reports a conflict for duplicated fields.
7Analyzing the query plan?
The Apollo Router outputs the query plan as JSON in debug mode, showing order and parallelism of subgraph calls.
8What is a schema registry?
Central management of subgraph versions and actual field usage, the basis for composition checks before every deploy.
9Migrating from stitching to federation?
A complete reimplementation of the linking logic: type merging configs become @key directives and reference resolvers per subgraph.
10Tooling needed in production?
Rover for composition, an Apollo Router, and a schema registry with composition checks in the CI pipeline before every merge.