← [ BACK_TO_NOTES ]

GraphQL Subscriptions

GraphQL Subscription 让客户端声明并持续接收某类服务端事件,解决当前客户端没有亲自发起变化时的实时同步;它与 Apollo normalized cache、WebSocket/SSE transport、共享 PubSub 与可靠事件恢复属于不同层次。

#concept #growing #api #graphql

GraphQL Subscriptions

一句话定义

Subscription 是 GraphQL 的实时 operation:客户端声明自己订阅哪类数据变化,服务端在事件发生后持续推送符合 Schema 的结果。

subscription {
  diagnosisUpdated(userId: "u1") {
    id
    summary
    updatedAt
  }
}

它表达的是“订阅什么数据”,而不是规定底层必须使用哪一种网络传输。

与 Query / Mutation 的关系

Query
= 请求一次当前数据

Mutation
= 当前客户端主动发起一次显式写操作

Subscription
= 持续接收服务端发生的数据变化

三者共享同一套 GraphQL Schema 和字段选择模型。

为什么自己发起 Mutation 时通常不需要 Subscription

假设 Client A 自己执行:

mutation {
  updateTask(id: "123", input: { status: DONE }) {
    id
    status
  }
}

服务端返回:

Task:123
status = DONE

Apollo Client 可以根据 Mutation response 中的 entity identity 把自己的 normalized cache 更新:

Client A Mutation
→ Server update
→ Mutation response
→ Apollo cache merge
→ Client A UI 更新

因此 Client A 通常不需要再靠 Subscription 听到“你刚才自己改了 Task 123”。

注意:这依赖响应足以描述受影响状态。列表 membership、派生字段、多实体副作用等场景仍可能需要手动 cache reconciliation 或 refetch。

Subscription 真正解决什么问题

更典型的是:当前客户端没有亲自发起这次变化。

例如:

用户 A / 设备 A / 后台 Job / Agent
→ 修改 Task 123

用户 B 的页面
→ 没有发起这次 Mutation
→ 本地 cache 不会凭空知道服务器已经变化

此时可以:

Server publishes taskUpdated
        ↓
Subscription
        ↓
Client B 收到 Task:123 新状态
        ↓
Apollo normalized cache merge
        ↓
Client B UI 更新

所以一个很实用的区分是:

Mutation reconciliation 解决“我自己写完以后,本地怎么跟上”;Subscription 解决“别人、别的设备或服务器自己改了以后,我怎么及时知道”。

单用户也可能需要实时同步

“只有一个账号/用户”不等于所有变化都由当前页面亲自发起。

仍可能有:

  • 同一用户多个 tab;
  • 手机和电脑同时在线;
  • 后台任务改变状态;
  • Agent / Worker 完成异步任务;
  • 定时任务或 webhook 更新数据;
  • 另一个服务修改共享状态。

因此判断是否需要 Subscription,关键不是“有几个用户”,而是:

当前客户端是否能从自己已经收到的 response 中可靠知道这次服务端状态变化。

Subscription 不等于 WebSocket

GraphQL Subscription 是应用层语义;WebSocket 是常见 transport。

GraphQL Subscription
        ↓
subscription protocol / execution
        ↓
WebSocket(常见)或其他 transport

所以:

“我订阅什么”与“字节怎样持续传输”是两个不同问题。

与 SSE 的关系

SSE 更接近一个单向 server → client transport:

HTTP connection
   ↓
server events
   ↓
client

GraphQL Subscription 更关注:

Schema 中声明订阅字段
   ↓
客户端选择字段
   ↓
服务端按 GraphQL 结果形状推送

因此二者不是简单竞争关系。

GraphQL Subscription 更自然的场景

  • 系统本身已经以 GraphQL 为主要 API;
  • 实时数据也是 Schema 数据图的一部分;
  • 客户端希望继续使用 GraphQL 类型、字段选择和 cache integration。

SSE 更自然的场景

  • 单向服务端推送足够;
  • AI token/event streaming;
  • 事件本身已经有稳定 event envelope;
  • 希望直接利用 HTTP 流式响应和简单重连模型。

BodySense 的 Agent streaming 属于后者:text delta / tool / state / safety 是运行时事件,不需要为了“实时”就强行包装成 GraphQL Subscription。

服务端怎样产生 Subscription 事件

常见逻辑:

Mutation / Worker / Domain Event
  ↓
业务状态更新成功
  ↓
publish event
  ↓
Subscription subscribers

Subscription Resolver 通常不是不停轮询数据库,而是消费某种 Pub/Sub 或事件流,再把事件映射成 GraphQL Schema 定义的 payload。

为什么单机内存 PubSub 在多实例下不够

假设:

Client A ─WebSocket→ Instance A
Client B ─Mutation → Instance B

如果 Instance B 只执行:

in-memory PubSub.publish(taskUpdated)

那么消息只存在于 Instance B 自己的进程内存。

Instance A 并不知道发生了事件:

Instance B memory PubSub
        X
Instance A memory PubSub

于是连接在 Instance A 的 Client A 收不到更新。

多实例部署通常需要共享消息基础设施:

Instance B
  ↓ publish
Redis Pub/Sub / NATS / Kafka / message broker
  ↓
Instance A
  ↓
Subscription connection
  ↓
Client A

Kafka 可以承担这个职责,但简单的低延迟广播并不必然需要 Kafka;Redis Pub/Sub、NATS 等可能更轻量。具体选择取决于是否需要持久化、顺序、回放、吞吐与交付语义。

Subscription 与 Apollo Normalized Cache 的分工

这是最容易混淆的一组概念:

Subscription
→ 解决“远端变化怎样来到当前客户端”

Normalized Cache
→ 解决“数据来到以后怎样识别实体、合并并通知 UI”

例如:

Subscription payload:
{ __typename: "Task", id: "123", status: "DONE" }
        ↓
Apollo identify
        ↓
Task:123
        ↓
merge status = DONE
        ↓
引用 Task:123 的 UI 更新

实时传输和本地缓存 reconciliation 是前后两个阶段。

Subscription 仍然没有自动解决恢复

连接断开之后仍要回答:

漏掉的事件怎么办?
如何知道最后收到哪里?
重复事件如何去重?
连接恢复是否等于业务 run 恢复?

GraphQL Subscription 本身不会替你自动创造 durable event log、cursor、sequence 或 idempotency。

这和你已经学过的 BodySense 原则一致:

transport connection
!= durable business execution

如果业务要求可靠补发/回放,可能需要 durable event log、cursor/sequence、消息队列或重新 Query authority,而不仅是一个在线 WebSocket。

核心判断

Subscription 解决“用 GraphQL 描述并接收当前客户端之外的实时变化”,WebSocket/SSE 解决“怎样持续传输”,Normalized Cache 解决“收到数据后怎样合并”,共享 broker 解决“事件怎样跨实例传播”,durable event log/cursor 解决“断线以后怎样可靠恢复”。

创建于 2025/1/1 更新于 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
关闭浮层 / 取消命令;输入框内清空文本