← [ BACK_TO_NOTES ]

GraphQL Query 与 Resolver 的执行链

把 GraphQL Query 的字段选择与 Resolver 的逐字段执行连接起来,理解 Schema、Resolver、Context、中间件、Service 与数据库之间的工程边界。

#synthesis #growing #api #graphql

[!info] related notes

GraphQL Query 与 Resolver 的执行链

为什么要一起理解

Query 是客户端声明“我要什么”,Resolver 是服务端回答“这个字段怎么得到”。二者通过 Schema 中的字段定义连接。

type Query {
  user(id: ID!): User
}

type User {
  id: ID!
  name: String!
  latestDiagnosis: Diagnosis
}

客户端:

query GetUser($id: ID!) {
  user(id: $id) {
    name
    latestDiagnosis { summary }
  }
}

执行链

可以把一次 GraphQL 查询看成一棵字段执行树:

Query.user(id)
   ↓ resolver
User
   ├─ name
   └─ latestDiagnosis
         ↓ resolver
      Diagnosis.summary

典型 resolver 接收四类信息:

parent   上一级字段返回的对象
args     当前字段参数
context  本次请求共享上下文
info     当前 GraphQL 执行信息

例如:

const resolvers = {
  Query: {
    user: (_parent, { id }, ctx) => ctx.userService.getById(id),
  },
  User: {
    latestDiagnosis: (user, _args, ctx) =>
      ctx.diagnosisService.getLatestByUserId(user.id),
  },
};

Resolver 与 REST Controller 的关系

REST 常见执行入口是:

Route → Controller / Handler → Service → Repository

GraphQL 更像:

Schema field → Resolver → Service → Repository

其中 Root Resolver:

Query.user
Mutation.updateUser

非常接近 REST Controller / Handler:负责接 API 参数、读取请求上下文、调用 Use Case / Service、把结果映射回 API contract。

但 GraphQL 还有 REST Controller 没有完全一一对应的 Nested Field Resolver:

User.assessments
Assessment.owner
Goal.tasks

它们是在字段级继续解析关系数据。因此更准确的说法是:

Resolver 是 GraphQL Schema 与应用层之间的 Adapter;Root Resolver 很像 Controller,Nested Resolver 更像字段级的数据解析 Adapter。

Resolver 不应该成为业务层

教学示例经常直接写:

User: {
  assessments: user =>
    prisma.assessment.findMany({ where: { userId: user.id } })
}

这能解释 Resolver 如何取数据,但真实工程更推荐:

Resolver
  ↓
Service / Use Case
  ↓
Repository
  ↓
Prisma / SQL
  ↓
Database

例如:

User: {
  assessments: (user, _args, ctx) =>
    ctx.assessmentService.listByUserId(user.id)
}

业务规则、事务编排、资源级授权应尽量留在 Service / Policy,而不是散落在 GraphQL transport 层。

Schema 不会自动查询数据库

type Query {
  user(id: ID!): User
}

只表示:

API 有一个 user 字段
需要 ID 参数
返回 User

它并没有规定:

SELECT * FROM users WHERE id = ?

真正的数据来源由 Resolver / Service 决定,可以是:

PostgreSQL
Redis
REST API
另一个 GraphQL service
文件
纯计算

所以 GraphQL 不会自动决定 JOIN,也不会自动优化 SQL。

Selection Set 决定本次需要哪些字段

Schema 中存在:

type User {
  name: String!
  assessments: [Assessment!]!
}

不代表每次获取 User 都会加载 assessments。

如果客户端只请求:

user(id: "42") {
  name
}

则 User.assessments 的自定义 Resolver 通常不会执行。

只有请求:

user(id: "42") {
  name
  assessments { score }
}

才需要沿着 User.assessments 继续解析。

中间件与请求边界在哪里

GraphQL 并没有把所有 middleware 消灭,而是出现了多个边界。

1. HTTP Middleware

GraphQL 请求通常仍通过 HTTP 进入:

POST /graphql
  ↓
CORS
request-id
logging
rate limiting
body size / compression
  ↓
GraphQL HTTP handler

这些仍然适合普通 Web 框架 middleware。

2. GraphQL Context

每一次 GraphQL Request 建立一次 request-scoped context:

authenticate credential
request / trace id
services
request-scoped DataLoaders

然后这一请求中的所有 Resolver 共享它。

3. Resolver wrapper / directive / plugin

一些 GraphQL 特有横切逻辑可以放在:

resolver wrapper
schema directive
server plugin / hook

但不要把复杂业务授权全部抽成“万能 middleware”。

4. Service / Policy

真正依赖资源状态的 Authorization:

User 42 能否修改 Assessment 789?

通常应由 Service / Policy 判断,因为它需要结合数据库中的 ownership、组织关系、资源状态等事实。

Context 的正确角色

context 适合放“本次请求共享且可信的依赖/身份信息”,例如:

currentUser / principal
requestId
services
request-scoped loaders

它不应该成为随意塞全局状态的垃圾桶。

认证通常先在 transport/request 边界解析 credential,再把得到的可信 identity 放入 context;resolver 把 actor identity 传给 Service,Service / Policy 完成资源级授权。

Context
→ 你是谁?

Service / Policy
→ 你能不能访问这个具体资源?

最重要的不变量

  • Query 只能选择 Schema 公开的字段;
  • Resolver 决定字段如何计算,而不是 Schema 自动查数据库;
  • Resolver 可以访问数据库、Service、其他 API,也可以纯计算;
  • Selection Set 决定本次实际需要解析哪些字段;
  • 同一个对象字段可能被大量父对象反复调用,因此逐字段执行很容易引出 N+1;
  • Resolver 应保持薄:业务规则仍应放在可复用 Service/Domain 层,而不是散落在 GraphQL 层。

常见误解

  • 每个字段都必须写 resolver:不一定。许多实现对同名属性支持默认 resolver。
  • GraphQL 自动优化 SQL:不会。GraphQL 只知道字段执行,不知道你的数据库最佳查询计划。
  • Resolver 就是业务层:不推荐。它更接近 API adapter/handler。
  • Resolver 就完全等于 REST Controller:Root Resolver 很接近,但 Nested Field Resolver 是 GraphQL 特有的重要差异。
  • Schema 定义了关系字段就一定会查关系数据:不会;客户端没选择该字段时,对应 Resolver 通常不会执行。
创建于 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
关闭浮层 / 取消命令;输入框内清空文本