为什么会有 FederationWhy Federation exists
一张大 schema 拆成几个服务,代价是什么,换来什么。One big schema split across several services: what it costs and what you get.
这一页有什么On this page6
- 说清单体 GraphQL 在大团队里的具体痛点Explain the concrete problems one single GraphQL service causes in a large team
- 用 Users / Products / Reviews 这个经典例子解释拆分Use the standard Users / Products / Reviews example to explain the split
- 说清 Federation 相比「客户端自己拼」好在哪Explain why Federation is better than letting the client join the data itself
- 认出本项目里那个不在仓库里的第三方 subgraphIdentify the subgraph this project refers to but does not contain
书面题 1 直接问「User subgraph 高延迟时如何影响依赖它的 subgraph」。答这道题的前提是理解 Router 的查询计划是串行的。Written question 1 asks directly how high latency in the User subgraph affects the subgraphs that depend on it. To answer it you need to know that the Router runs parts of its query plan one after another.
单体 GraphQL 的痛点The problems with one single GraphQL service
不是技术问题,是组织问题。This is not a technical problem. It is an organizational one.
假设一家公司有账号团队、商品团队、订单团队、物流团队。 如果只有一个 GraphQL 服务,那么:
- 一份 schema 四个团队改。合并冲突、review 排队、谁都不敢删字段。
- 一起发布。物流团队改一行,整个 API 网关要重新部署; 任何一个团队的 bug 会拖垮所有查询。
- 技术栈被绑死。订单团队想用 Java,账号团队想用 Node —— 单体做不到。
Federation 的做法:每个团队维护自己的 subgraph(一个独立的 GraphQL 服务,有自己的 schema、自己的部署、 自己的语言),然后由一个 Router把它们组合成一张对客户端而言完整的supergraph。
客户端完全看不出背后有几个服务。它眼里就是一张图,user 上就是有 orders 字段。
Picture a company with an accounts team, a products team, an orders team and a shipping team. With a single GraphQL service:
- Four teams edit one schema. Merge conflicts, review queues, and nobody dares delete a field.
- They ship together. The shipping team changes one line and the whole API gateway is redeployed; a bug from any one team drags down every query.
- The stack is locked in. The orders team wants Java, the accounts team wants Node — a monolith cannot have both.
What Federation does: every team keeps its own subgraph (an independent GraphQL service with its own schema, its own deploys, its own language), and one Router composes them into a single supergraph that looks whole to the client.
The client cannot tell how many services sit behind it. It sees one graph, where user simply has an orders field.
经典例子:Users / Products / ReviewsThe standard example: Users / Products / Reviews
这是 Apollo 官方文档用了很多年的例子,值得记住:
关键在 Review 那一行:Reviews 服务需要说「这条评论是谁写的、评的哪个商品」, 但它自己没有用户表和商品表。它只有 id。
Federation 的答案是:Reviews 服务在自己的 schema 里声明一个只有 id 的 User 和 Product, 标上 @key,剩下的字段(name、price) 由 Router 去对应的 subgraph 补齐。
本项目就是这个模式的一半。node-subgraph 是「Orders 服务」, 它需要往 User 上挂一个 orders 字段, 但它不拥有 User。所以它的 schema 里有:
Apollo’s own docs have leaned on this example for years. Keep it in your head:
The line that matters is Review: the Reviews service has to say who wrote a review and which product it is about, but it has no user table and no product table. All it has is ids.
Federation’s answer: the Reviews service declares a User and a Product that carry nothing but an id in its own schema, marks them with @key, and lets the Router fill the remaining fields (name, price) from the subgraphs that own them.
This project is one half of that pattern. node-subgraph is the “Orders service”. It needs to hang an orders field off User, but it does not own User. So its schema contains:
graphql-federation-practice/node-subgraph/src/schema.graphql为什么不让客户端自己拼Why not let the client join the data itself
客户端也可以先请求 Accounts 拿 user,再请求 Orders 拿订单, 自己拼起来。为什么要 Router?
- 往返次数。客户端拼需要 N 次网络往返(而且往往是串行的, 因为第二个请求需要第一个的 id)。Router 在数据中心内部完成这些跳转, 客户端只发一次请求。移动网络下这个差别是几百毫秒。
- 一致的类型系统。客户端不需要知道「哪个字段在哪个服务」—— 这个知识是会变的(服务拆分/合并), 写进客户端就等于每次后端重构都要发新版 App。
- 统一的横切关注点。鉴权、限流、缓存、可观测性在 Router 这一层做一次就行。
代价也要说清楚:多了一跳(Router 本身的延迟)、 查询计划可能是串行的(下一节会展开)、 组合失败会让整个 supergraph 起不来。书面题 1 问的就是这里的第二条。
A client could just as well call Accounts for the user, then call Orders for the orders, and stitch them together itself. So why a Router?
- Round trips. Stitching on the client costs N network round trips (usually serial ones, since the second request needs an id from the first). The Router makes those hops inside the data centre and the client sends one request. On a mobile network that difference is hundreds of milliseconds.
- One consistent type system. The client does not need to know which field lives in which service — that knowledge changes (services split and merge), and baking it into the client means shipping a new app build after every backend refactor.
- Cross-cutting concerns in one place. Auth, rate limiting, caching and observability are done once, at the Router.
Be straight about the cost too: one extra hop (the Router’s own latency), query plans that may run serially (the next lesson expands on this), and a failed composition that keeps the whole supergraph from starting. Written question 1 is asking about the second one.
本项目里哪些东西不在仓库里What this project refers to but does not contain
这一点必须诚实说清楚,否则你会花时间找不存在的文件。This has to be said plainly, or you will spend time looking for files that do not exist.
审计确认的事实:
| 东西 | 在仓库里吗 | 怎么知道它存在 |
|---|---|---|
| Orders subgraph | ✅ 就是 node-subgraph/ | — |
| Accounts subgraph(拥有 User) | ❌ 不在 | schema 里 User.id 标了@external;resolver 注释写了from Accounts subgraph |
| Router / Gateway | ❌ 不在 | 没有 supergraph.yaml、没有 rover、 没有 @apollo/gateway 依赖 |
| java-service(REST) | ✅ 在,但不是 subgraph | 纯 Spring Web REST,没有任何 GraphQL 依赖; subgraph 也不调它(用的是 mock 数据源) |
所以 java-service 和 node-subgraph 在代码层面毫无关联。它们是同一场考试的两道题,共享 Order 这个领域概念和 correlation-id 这套可观测性思路,但不互相调用。别去找那个不存在的 HTTP client。
那怎么在本地验证 Federation 部分?两个办法,下一节会实际用: 查询 { _service { sdl } } 拿 federation SDL, 以及直接调 _entities —— 这两个正是 Router 会对你的 subgraph 发的请求。
Facts confirmed by the audit:
| Thing | In the repo? | How you know it exists |
|---|---|---|
| Orders subgraph | ✅ it is node-subgraph/ | — |
| Accounts subgraph (the one that owns User) | ❌ not here | the schema marks User.id as @external; a resolver comment says from Accounts subgraph |
| Router / Gateway | ❌ not here | no supergraph.yaml, no rover, no @apollo/gateway dependency |
| java-service (REST) | ✅ here, but not a subgraph | plain Spring Web REST with no GraphQL dependency at all; the subgraph never calls it either (it uses mock data sources) |
So java-service and node-subgraph have no connection at the code level. They are two questions in one exam that share the Order domain concept and the correlation-id approach to observability, but they never call each other. Do not go hunting for that HTTP client.
Then how do you verify the Federation part locally? Two ways, both used in the next lesson: query { _service { sdl } } for the federation SDL, and call _entities directly — those two are exactly the requests the Router would send your subgraph.
动手做Get your hands on it
填空只是过渡。真正掌握的标准,是在没有答案的时候从头写出来 —— 所以做完 L2 之后一定要往 L3、L4 走。Filling blanks is a stepping stone. The real bar is writing it from nothing, so once L2 is comfortable, push on to L3 and L4.
下面哪一条最准确地描述了 Federation 要解决的核心问题?
Which of these describes most accurately the core problem Federation sets out to solve?
下面哪些是这个 assessment 仓库里没有的?(多选)
Which of these are not in this exam repository? (Select all that apply.)
这题是多选。More than one answer is correct.
换一道题也能用Works on other problems too
考试不会原题重考。真正能带走的是「看到这种信号 → 伸手去拿这个解法」。The exam will not reuse the same question. What you take away is the reflex: see this signal, reach for that solution.
- Federation 首先解决的是 schema 所有权的组织问题,不是性能问题。Federation first solves an organizational problem about who owns which part of the schema, not a performance problem.
- 每个 subgraph 独立部署、可用不同语言;Router 组合成一张 supergraph。Each subgraph deploys on its own and can use a different language. The Router combines them into one supergraph.
- 本仓库只有 Orders subgraph;Accounts subgraph 和 Router 都不在。This repository contains only the Orders subgraph. The Accounts subgraph and the Router are not here.
- java-service 是纯 REST,不是 subgraph,也不被 subgraph 调用。java-service is plain REST. It is not a subgraph, and no subgraph calls it.
- 本地验证 Federation 的两个办法:{ _service { sdl } } 和 _entities 查询。Two ways to check Federation locally: the { _service { sdl } } query and the _entities query.