← [ BACK_TO_NOTES ]

GraphQL MOC

GraphQL 学习地图:从历史与 REST 对照开始,沿 Schema、Resolver、Mutation/Validation、Apollo normalized cache、Auth、DataLoader、Fragment 与 Subscription 建立完整工程心智模型。

#moc #growing #api #graphql

[!info] related notes

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. 先建立历史与定位

  1. GraphQL 的历史与生态定位
  2. GraphQL vs REST
  3. GraphQL

先理解 GraphQL 为什么在 Facebook 移动端数据需求中出现,以及它今天为什么与 REST 长期共存;再看清 GraphQL 解决的是复杂客户端数据组合与强类型 API contract,同时把新复杂度放到了 Schema、Resolver、Client Cache 与执行治理上。

2. 服务端契约与一次完整请求

  1. GraphQL Schema
  2. GraphQL 一次请求的端到端执行
  3. GraphQL Query 与 Resolver 的执行链
  4. GraphQL Mutation 与错误语义

这一段建立最核心链路:

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. 客户端与缓存

  1. Apollo Client 在 GraphQL 中的角色
  2. GraphQL Normalized Cache

重点不是背 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 与请求级依赖

  1. GraphQL Auth Context

核心职责:

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

  1. GraphQL N+1 Query Problem

客户端的一次漂亮嵌套 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 更易复用和维护。

  1. GraphQL Subscriptions

这里要把五个层次分清:

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 反而更简单。

相邻知识地图

创建于 2026/9/11 更新于 2026/9/15
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本