GraphQL MOC
GraphQL 学习地图:从历史与 REST 对照开始,沿 Schema、Resolver、Mutation/Validation、Apollo normalized cache、Auth、DataLoader、Fragment 与 Subscription 建立完整工程心智模型。
[!info] related notes
- 相关 MOC: backend-dev-moc、tanstack-query-moc、caching-moc
- 关系笔记: frontend-backend-realtime-communication
- 相关资源:
GraphQL MOC
知识总览
filters:
and:
- 'file.ext == "md"'
- 'file.inFolder("z")'
- 'this.file.links.contains(file)'
- 'file.path != this.file.path'
formulas:
knowledge: 'file.asLink(title)'
properties:
formula.knowledge:
displayName: 知识
note.description:
displayName: Description
note.mastery:
displayName: Mastery
views:
- type: table
name: 知识总览
order:
- formula.knowledge
- note.description
- note.mastery
columnSize:
formula.knowledge: 280
note.description: 760
note.mastery: 56
覆盖范围
这张地图服务于“理解 GraphQL 的工程模型”,不是 API 语法大全。
目标是能顺着一条完整链路回答:
GraphQL 为什么出现
→ 它和 REST 到底是什么关系
→ Schema 怎样成为前后端合同
→ 前端怎样自由组合字段
→ Resolver 怎样进入 Service / Repository
→ Mutation 怎样改变状态并分层验证
→ Apollo 怎样维护 normalized cache
→ Auth Context 与业务 Authorization 怎样分工
→ DataLoader 怎样治理 N+1
→ Fragment 怎样复用 Selection Set
→ Subscription 怎样同步外部实时变化
推荐阅读顺序
1. 先建立历史与定位
先理解 GraphQL 为什么在 Facebook 移动端数据需求中出现,以及它今天为什么与 REST 长期共存;再看清 GraphQL 解决的是复杂客户端数据组合与强类型 API contract,同时把新复杂度放到了 Schema、Resolver、Client Cache 与执行治理上。
2. 服务端契约与一次完整请求
这一段建立最核心链路:
Backend Schema
↓ publish / introspection
Frontend Operation
↓
GraphQL validation + execution
↓
Resolver / API Adapter
↓
Service / Policy
↓
Repository / Database / external API
读完应该能解释:
- Schema 是 API Contract / Type System,不是数据库 Schema;
- Schema 不会自动执行 SQL;
- Root Resolver 很像 REST Controller,但 Nested Field Resolver 是 GraphQL 特有边界;
- Resolver 应尽量薄,业务规则与资源级授权放 Service / Policy;
- Selection Set 没选中的关系字段通常不需要执行对应 Resolver;
- GraphQL middleware 仍分 HTTP middleware、Context、Resolver wrapper/plugin、Service/Policy 等层。
3. Mutation、验证与错误边界
Mutation 的完整思路不是“调用一次写接口”,而是:
GraphQL Schema
→ shape / required / primitive type
Runtime Validation
→ format / range
Service / Domain
→ business invariant / authorization
Database Constraint
→ concurrency-safe persistence invariant
Persistence error
→ application/domain error
→ stable GraphQL public error
Service 提前检查用于早失败和友好错误;数据库 UNIQUE / FK / CHECK 等负责最终持久化不变量,尤其防住并发竞态。
4. 客户端与缓存
重点不是背 Apollo API,而是理解:
TanStack Query
→ query-centric cache
→ queryKey → result
Apollo Client
→ entity / graph-centric normalized cache
→ __typename + id → Entity
二者都是 server-state projection 工具。
不要误解为:
TanStack Query 只能 invalidate/refetch ❌
Apollo 只看 Mutation input 就自动改缓存 ❌
更准确的是:
- TanStack Query 也能
setQueryData、optimistic update、按 mutation response patch; - Apollo 常根据 Mutation response 中可识别实体做 normalization / merge;
- Mutation 只返回
success、列表 membership 改变或派生状态变化时,Apollo 仍可能需要显式 cache update / refetch; - optimistic update 是客户端先预测状态,再用服务端 response 校正,不等于默认 normalized merge。
5. Auth 与请求级依赖
核心职责:
HTTP/Auth boundary
→ 验证 credential
Context
→ 当前请求是谁 + services + request-scoped loaders
Resolver
→ GraphQL Adapter
Service / Policy
→ 这个 actor 能否访问这个具体 resource
尤其注意:
Schema 中可查询
≠
当前用户有权查询
GraphQL 字段可从多个路径到达,因此 BOLA / IDOR 风险不能只靠某一个 Root Resolver 的检查。
6. 数据库性能:N+1 与 DataLoader
客户端的一次漂亮嵌套 Query,可能在服务端变成:
1 次 users
+ N 次 assessments
= 1 + N SQL
DataLoader 的正确模型:
request-scoped batcher + memoizer
例如同一次 request:
load(1)
load(2)
load(3)
load(2)
↓
[1,2,3]
↓
WHERE user_id IN (1,2,3)
Loader 通常每个 GraphQL Request 创建一次,同一 Request 的相关 Resolver 共享;不要用一个永久全局 Loader 缓存所有用户数据。
7. Fragment 与实时同步
Fragment 本质是客户端 Selection Set 的复用:
fragment UserDetails on User {
id
name
avatar
}
它不会在服务端创建新的 endpoint / Resolver,只是让 operation 更易复用和维护。
这里要把五个层次分清:
Mutation reconciliation
→ 当前客户端自己写完以后怎么更新本地 projection
Subscription
→ 当前客户端之外的变化怎样实时到达
WebSocket / SSE
→ 字节怎样持续传输
Shared broker / PubSub
→ 多个服务实例之间怎样传播实时事件
Durable event log / cursor
→ 断线后怎样补发、回放、去重
当前客户端自己执行 Mutation,且 response 返回足够的更新实体时,通常不需要 Subscription 来通知自己。
另一个用户、另一个 tab/设备、后台 Job、Agent、Webhook 修改服务端状态时,当前客户端并不会凭空知道;此时才需要 Subscription、SSE/WebSocket event、Polling 或重新 refetch 等机制。
多实例服务中单机内存 PubSub 不够,因为 Instance A 与 Instance B 不共享进程内存;通常需要 Redis Pub/Sub、NATS、Kafka 或其他共享 broker。
一张总图
GraphQL Schema
/ | \
docs / codegen validate server execution
↓ ↓ ↓
React UI → Apollo Client → Operation → Resolver / Adapter
↑ ├─ Context / Identity
│ ├─ Service / Policy
│ ├─ DataLoader / Batch
│ └─ Repository
│ ↓
│ PostgreSQL / APIs
│ ↓
│ Response
│ ↓
└────────────── Normalized Cache ←─────┘
↑
│ Subscription payload
│
Shared Event / PubSub
当前学习验收状态
本轮 Full Stack Open Part 8 Gap Check 已覆盖:
Schema + Resolver ✅
Mutation + Validation + Error ✅
Apollo Client + Normalized Cache ✅
N+1 + DataLoader ✅
Auth Context + Authorization ✅
Fragment ✅
Subscription + multi-instance ✅
当前 mastery 记为 3 / 5:能理解并解释主要机制与工程边界。
暂不标 4,因为还缺少一次完整 GraphQL 应用的实际实现、调试与性能验证证据。
与 BodySense 当前架构的关系
BodySense 当前正式架构仍可以保持:
React
↓
TanStack Query + REST
↓
Go API
Agent realtime
↓
SSE event stream
GraphQL 在课程中更适合作为隔离 Lab 和架构对照。学习目标是获得新的 API 设计模型,而不是为了“学过”就迁移生产系统。
学习检查点
完成本专题后,应该能自己解释:
- GraphQL 为什么出现,以及为什么没有取代 REST;
- Schema 为什么是 API contract,而不是 DB Schema;
- Query、Selection Set、Root Resolver、Nested Resolver 各负责什么;
- Resolver 为什么应进入 Service / Repository,而不是成为胖业务层;
- GraphQL Context 与 Authentication / Authorization 怎样分层;
- Mutation 输入为什么仍需 Runtime Validation、Service invariant 和 DB constraint;
- GraphQL partial data / errors 与 null bubbling;
- Apollo normalized cache 为什么需要 entity identity;
- Apollo 自动 merge 为什么主要依赖 Mutation response 而不是单纯 input;
- TanStack Query 与 Apollo 在 query-centric / entity-centric cache 上的差异;
- DataLoader 为什么是 request-scoped batcher + memoizer;
- Fragment 为什么只是 Selection Set 复用;
- 当前客户端 Mutation 与外部变化为什么是两种缓存同步问题;
- Subscription、WebSocket/SSE、shared broker、durable replay 的层次差异;
- 为什么一条 GraphQL Query 可能产生 N+1 SQL;
- 什么情况下 REST 反而更简单。