A client's GraphQL schema, built up over the last year, had grown to the point where every backend team touching it needed to coordinate changes through a single shared codebase — exactly the coordination bottleneck that microservices are supposed to avoid, just relocated into the GraphQL layer instead of the REST layer.
The specific coordination problem we were solving
Three backend teams — one owning user accounts, one owning the product catalog, one owning order and fulfillment logic — all needed to ship changes to what had become one shared GraphQL schema and one shared codebase implementing it. A change from any one team required a review and a deploy coordinated with the other two, even when the actual change touched only that team's own part of the graph, which had slowed deploys to a fraction of what each team could achieve independently and had started to produce exactly the kind of "whose turn is it to deploy" friction a well-run microservices architecture is supposed to avoid.
What Apollo Federation actually does
- Each backend team's service defines and owns its own subgraph — a portion of the overall schema — independently of the others
- A gateway service composes all the subgraphs into one unified schema that clients query against, with no visible difference from a client's perspective compared to querying a single monolithic schema
- Services can reference and extend types owned by another service — an order type extending a base user type owned by the accounts team, for instance — through a small set of federation-specific schema directives
Setting up entity references across service boundaries
Getting entity references working correctly between services took real learning, since it requires each service to declare which fields uniquely identify one of its types, and requires any other service extending that type to implement a resolver capable of fetching the extending fields given just that identifying key. Our first attempt at this got the key fields wrong on one type, which produced confusing runtime errors at the gateway level that took longer than we would like to admit to trace back to a schema-level misconfiguration rather than an actual bug in either service's own resolver code.
The gateway as new infrastructure to operate
The gateway itself became a new piece of infrastructure to operate and monitor, sitting in the critical path of every single client request regardless of which underlying service actually resolves a given field. This is a real new single point of failure that did not exist in the single-schema architecture it replaced, and we deployed it with the same redundancy and monitoring rigor we would apply to any other critical-path service, rather than treating it as a lightweight proxy that could get away with less operational attention than the services behind it.
How this changed each team's actual deploy cadence
Once the migration was complete, each of the three backend teams could deploy their own subgraph independently, without coordinating with the other two, as long as they respected the shared type contracts at the federation boundaries. The accounts team, in particular, went from deploying roughly weekly, coordinated with the other two teams' own release schedules, to deploying multiple times a week on their own schedule, which was the concrete organizational outcome we were actually trying to achieve — the technical architecture change was in service of that organizational goal, not an end in itself.
A schema-ownership convention we had to establish
Federation solves the deployment coordination problem but introduces a new one: a schema change at a federation boundary — modifying a type another team's service extends — can still break another team's service even though the two teams deploy independently. We established a convention requiring any change to a boundary type to go through a lightweight cross-team review specifically for that type, even though each team's own, non-boundary schema changes need no such review, which keeps the coordination overhead scoped only to the genuinely shared surface area rather than reintroducing the original bottleneck across the whole schema.
Query planning overhead we had to account for
A query spanning multiple subgraphs requires the gateway to plan out which underlying services to call, in what order, and how to stitch their responses together, which adds a small amount of latency compared to a single service resolving an equivalent query directly against its own local data. For most queries this overhead is negligible, but a small number of our client's most complex queries, ones that touch all three subgraphs in a single request, saw a measurable latency increase after the migration, which we addressed by having the accounts and catalog teams collaborate on restructuring one particularly expensive cross-subgraph reference to avoid an unnecessary extra network hop the original schema design had introduced without either team fully realizing it at the time.
Testing a federated schema differently than a monolithic one
Our previous end-to-end test suite ran directly against the single monolithic schema, which no longer exists in the same form once each team owns its own subgraph independently. We now run each subgraph's own test suite independently, plus a smaller, focused suite of composition tests that spin up the actual gateway against all three real subgraphs to verify that federation-specific concerns — entity resolution across boundaries, in particular — still work correctly as an integrated whole. Maintaining both layers is more testing infrastructure than the single-schema setup needed, but it is what actually lets each team trust their own independent deploys without waiting on a slower, full-system test run for every change.
Why we did not — and would not — set this up on day one of a new GraphQL project
None of this complexity would have been justified at the start of this schema's life, when a single small team owned the entire thing and a single shared codebase was genuinely the simplest, most productive setup available. Federation is a response to an organizational problem — multiple teams needing independent ownership of one graph — that only exists once a project has actually grown to that point, and we would actively discourage a team from adopting Apollo Federation preemptively for a new project on the theory that they might need it eventually, since the added gateway infrastructure and cross-team schema conventions are real ongoing costs that only make sense once the coordination problem they solve is already real.
Running the old monolithic schema and the new federated one side by side during the cutover
Rather than cutting every client over to the federated gateway in one migration event, we ran the original monolithic schema and the new gateway side by side for about two weeks, with internal tooling gradually shifted over first and the client-facing mobile and web apps moved only once the gateway had proven stable under real internal traffic. This overlap period caught the entity-key misconfiguration described above under real, if internal, traffic conditions before any customer-facing request ever depended on it, which validated the extra caution of a gradual cutover rather than a single flag-the-switch migration event.
Results
For a schema that had genuinely outgrown a single team's ownership, federation solved a real organizational problem, not just a technical one, and the deploy-cadence improvement described above has held up several months in. We would not recommend this migration for a schema that has not yet actually outgrown a single team, since the gateway's added operational complexity is a real cost that only pays for itself once the coordination bottleneck it solves is a genuine, currently-felt problem rather than a hypothetical future one.
Documentation changes federation required
A single monolithic schema could be documented in one place, but a federated schema's true shape only fully exists once the gateway composes all three subgraphs together, which meant our old practice of each team documenting its own service in isolation no longer gave a new developer a complete picture. We now generate and publish the gateway's fully composed schema automatically after every deploy, giving every team, and any new developer joining any of the three teams, one authoritative reference for the graph's actual current shape rather than three separate documents that could each be individually accurate but collectively incomplete. We expect to publish an updated version of this write-up once we have a full year of federated production experience behind us, since several months in is still early enough that some of the longer-term maintenance tradeoffs may not yet be fully visible to us.