DrillLab
第 04 / 17 节LESSON 04 / 17约 11 分钟~11 min

为什么会有 FederationWhy Federation exists

一张大 schema 拆成几个服务,代价是什么,换来什么。One big schema split across several services: what it costs and what you get.

2 个练习2 exercisesFederation · 第 2 部分Federation · Part 2
这一页有什么On this page6
学完这节你会After this lesson you can
  • 说清单体 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
这在考试里考什么What the exam does with this

书面题 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.

§01

单体 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.

§02

经典例子: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:

Text三个服务,一张图Three services, one picture示意Illustrative
1Accounts 服务 Products 服务 Reviews 服务
2───────────── ───────────── ─────────────
3type User { type Product { type Review {
4 id id id
5 name name body
6 email price author: User ← 别人的类型
7} } product: Product ← 别人的类型
8 }
9
10客户端看到的(supergraph):
11type User { id name email reviews: [Review] }
12type Product { id name price reviews: [Review] }
13type Review { id body author: User product: Product }
GraphQL SDL本项目里的同一个模式The same pattern in this project源项目From source
1type User @key(fields: "id") {
2 id: ID! @external # 「id 由别的 subgraph 提供,我只是引用」
3 orders: [Order!]! # 「orders 是我贡献的字段」
4}
1type User @key(fields: "id") {
2 id: ID! @external # "another subgraph owns id; I only reference it"
3 orders: [Order!]! # "orders is the field I contribute"
4}
Source: graphql-federation-practice/node-subgraph/src/schema.graphql
orderResolvers.js 的注释原文写着「extend User entity from Accounts subgraph」—— 那个 Accounts subgraph 不在这个仓库里,但 schema 已经在跟它对话了。The comment in orderResolvers.js reads "extend User entity from Accounts subgraph". That Accounts subgraph is not in this repository, but the schema is already talking to it.
§03

为什么不让客户端自己拼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.

§04

本项目里哪些东西不在仓库里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:

ThingIn the repo?How you know it exists
Orders subgraph✅ it is node-subgraph/
Accounts subgraph (the one that owns User)❌ not herethe schema marks User.id as @external; a resolver comment says from Accounts subgraph
Router / Gateway❌ not hereno supergraph.yaml, no rover, no @apollo/gateway dependency
java-service (REST)✅ here, but not a subgraphplain 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.

练习Practice

动手做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.

L1认出来Spot itFederation 主要解决的是什么问题What problem does Federation mainly solve

下面哪一条最准确地描述了 Federation 要解决的核心问题?

Which of these describes most accurately the core problem Federation sets out to solve?

先选一个选项Pick an option first
L1认出来Spot it哪些东西不在这个仓库里What is not in this repository

下面哪些是这个 assessment 仓库里没有的?(多选)

Which of these are not in this exam repository? (Select all that apply.)

这题是多选。More than one answer is correct.

先选一个选项Pick an option first
迁移Transfer

换一道题也能用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.

「多团队共享一个 API」Several teams share one API
Federation:各自 subgraph + Router 组合Federation: one subgraph per team, combined by a Router
「我这个服务要引用别人的类型」Your service needs to refer to a type owned by another service
声明一个只有 @key 字段的类型,标 @externalDeclare that type with only its @key field and mark the field @external
找不到某个「应该存在」的文件You cannot find a file that seems like it should exist
先确认它是不是本来就不在仓库里First check whether it was ever in the repository
书面题问「某个 subgraph 慢了会怎样」A written question asks what happens when one subgraph is slow
从「查询计划可能串行」入手Start from the fact that the query plan may run steps one after another
这节的要点What to take away
  1. Federation 首先解决的是 schema 所有权的组织问题,不是性能问题。Federation first solves an organizational problem about who owns which part of the schema, not a performance problem.
  2. 每个 subgraph 独立部署、可用不同语言;Router 组合成一张 supergraph。Each subgraph deploys on its own and can use a different language. The Router combines them into one supergraph.
  3. 本仓库只有 Orders subgraph;Accounts subgraph 和 Router 都不在。This repository contains only the Orders subgraph. The Accounts subgraph and the Router are not here.
  4. java-service 是纯 REST,不是 subgraph,也不被 subgraph 调用。java-service is plain REST. It is not a subgraph, and no subgraph calls it.
  5. 本地验证 Federation 的两个办法:{ _service { sdl } } 和 _entities 查询。Two ways to check Federation locally: the { _service { sdl } } query and the _entities query.

接下来What next

  1. 把这一节的练习做掉Do this lesson’s exercises2 个,就在这一页上面 —— 别攒着最后一起做2 of them, further up this page — do not save them for later
    回到练习 ↑Back up to them ↑
  2. 接着看下一节Continue to the next lessonsubgraph 是怎么跑起来的How a subgraph starts up
    下一节Next lesson
读完并且做过上面的练习了吗?Read it and worked through the exercises above?
上一节:Previous: 非空、列表,和那个没有 price 的 inputNon-null, lists, and the input that has no price