Web Development

Adopting GraphQL federation as more teams own their own schema

DEV

Our GraphQL API started as a single schema owned by one team, which worked fine until three more teams started shipping features that needed their own types and resolvers. Every new feature meant a pull request against a shared schema file that half a dozen people were touching in the same sprint, and merge conflicts on the schema became a recurring source of friction that slowed every team down roughly equally, regardless of whose feature was actually causing the contention. Apollo Federation let each team own a subgraph independently rather than all merging changes into one file, and the migration turned out to be as much an organizational change as a technical one.

The bottleneck before federation

Before federation, shipping a new field on an existing type meant coordinating with whichever team currently had the schema file checked out for their own change, since a naive merge of two schema edits in the same region of the file would silently produce invalid GraphQL that only surfaced when someone tried to run codegen. We had informal rules about who could touch the schema when, enforced through a shared calendar of sorts, which is not a scalable process once more than two or three teams are contributing regularly.

The deeper problem wasn't the merge conflicts themselves but what they represented: a single point of coordination overhead that grew linearly with the number of teams touching the API, even though the actual features those teams were building had almost nothing to do with each other. A payments team adding a field to an Order type shouldn't need to coordinate with a marketing team adding a field to a Promotion type, but a single shared schema file forced exactly that coordination.

What federation actually changed

Each team's subgraph now deploys on its own schedule, which removed the deploy-coordination bottleneck that had been slowing every team down equally. A team can add, change, or remove a field on the types they own without touching another team's subgraph at all, and the gateway composes the full schema automatically from whatever subgraphs are currently deployed. Deploy frequency for the teams that adopted federation earliest roughly tripled within the first quarter, mostly because they were no longer waiting for a coordination window to land unrelated changes.

  • Each team's subgraph now deploys on its own schedule, which removed the deploy-coordination bottleneck that had been slowing every team down equally.
  • Schema composition happens automatically at the gateway, catching conflicting type definitions across subgraphs at build time rather than at runtime.
  • Entity resolution across subgraphs, where one team's subgraph extends a type another team owns, required careful design to avoid circular ownership that made reasoning about a type's source of truth genuinely confusing.

Entity extension, where one subgraph adds fields to a type primarily owned by another subgraph, is the feature that made the multi-team model actually work rather than just splitting the same file into pieces. A reviews team can extend the Product type, owned by the catalog team, with a reviews field, without either team needing write access to the other's subgraph. Getting the ownership boundaries right took a couple of iterations; our first attempt let too many subgraphs extend the same core types, which reintroduced a milder version of the original coordination problem.

The debugging cost

Debugging a query that spans multiple subgraphs is genuinely harder than debugging a single monolithic schema, and we've had to invest in better tracing to keep that manageable. A slow query might be slow because of the gateway's query planning, a specific subgraph's resolver, or the network hop between them, and distinguishing between those causes without proper instrumentation is close to guesswork. We now trace every subgraph call individually and surface the full query plan in our internal debugging tools, which took real engineering investment but has made incident response for GraphQL-related issues roughly as fast as it was before federation, if not faster given how much clearer the traces are than reading through a monolithic resolver's call stack used to be.

What we'd do differently

Federation is worth the added complexity once more than a couple of teams are contributing to the same GraphQL API. For a single-team API, it's overhead without a clear payoff, and we'd actively discourage adopting it prematurely just because it's the more modern approach. If we were rolling this out again, we'd establish clearer ownership guidelines for entity extension before the second team joined, rather than after we'd already accumulated a handful of extensions that made ownership ambiguous. We'd also invest in the distributed tracing setup before the migration rather than scrambling to build it after the first hard-to-debug production incident that spanned three subgraphs, since that incident is what finally convinced leadership the tooling investment was worth prioritizing.

Rolling out the gateway without downtime

Migrating a production API to federation without a maintenance window meant running the old monolithic schema and the new federated gateway side by side for a transition period, with traffic gradually shifted from one to the other behind a feature flag at the load balancer level. We started by routing a small percentage of internal, low-stakes traffic to the gateway, our own admin tools, before opening it up to customer-facing traffic. That staged rollout caught a handful of subtle differences in error formatting between the old schema and the federated gateway's error responses, differences that a client library further down the stack was actually depending on, before those differences reached real customers.

The cutover itself took about six weeks from first internal traffic to fully retiring the old monolithic schema, longer than the technical migration alone would have required, but the extra time bought us confidence that we weren't trading a coordination bottleneck for a customer-facing incident.

Schema governance in a federated world

Federation solves the file-level merge conflict problem, but it introduces a new one: without any oversight, nothing stops two teams from independently choosing incompatible names or shapes for conceptually similar fields, which just relocates the coordination problem from git merge conflicts to schema design inconsistency. We introduced a lightweight schema review step, not a full design committee, just a short async check from one person familiar with the whole federated schema, for any new type or significant field addition. That review has caught a few near-misses, two teams independently about to add a very similarly named field with subtly different semantics, before they shipped and became a permanent naming inconsistency baked into the public API.

Versioning and deprecating fields across subgraphs

Deprecating a field is harder in a federated schema than a monolithic one, since the team that owns the field can no longer just grep the codebase for every caller before removing it; other teams' subgraphs and client applications may depend on it without that dependency being visible in the owning team's own repository. We adopted a strict deprecation window, a field gets marked `@deprecated` with a reason and a removal date at least one quarter out, and we built a small usage-tracking tool at the gateway level that logs which deprecated fields are still being queried and by which client, which turned deprecation from a guessing game into something we could actually verify was safe before removing a field for good.

The tooling investment beyond tracing

Distributed tracing solved the debugging problem, but we ended up investing in a second piece of tooling almost as important: a schema diff tool that runs in CI on every subgraph pull request and flags any change that would break composition with the currently deployed versions of every other subgraph. Before that tool existed, a genuinely well-intentioned change in one subgraph occasionally broke gateway composition in a way that only surfaced when that subgraph actually deployed, well after the pull request had already been reviewed and merged. Catching composition breaks at pull-request time rather than deploy time moved an entire category of incident earlier in the pipeline, where it's dramatically cheaper to fix.

Onboarding a new team onto federation

Bringing a fifth team onto the federated schema more recently gave us a chance to see how much the process had improved since the original migration. What used to require direct hand-holding from whoever built the original federation setup is now largely self-service: a template subgraph repository, a short internal guide covering entity extension and the schema review process, and access to the same schema diff tooling every other team already uses. The new team shipped their first subgraph in under a week, a fraction of the multi-week ramp-up the original teams needed, which we credit mostly to codifying lessons from the first migration into tooling and documentation rather than leaving them as tribal knowledge.

The client-side experience

From the client application's perspective, federation is meant to be invisible, and for the most part it has been; queries look the same whether they're served by a monolithic schema or a federated one, since the gateway presents a single unified schema regardless of how many subgraphs sit behind it. The one place complexity leaked through to client developers was in error handling for partial failures, where one subgraph is down but others are healthy, a scenario that essentially couldn't happen under the old monolithic schema and that some existing client code hadn't been written to handle gracefully. We ended up documenting a standard pattern for handling partial GraphQL responses and updated our internal client libraries to surface partial failures more clearly, which has made this edge case far less confusing for teams building against the API day to day.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients