← [ BACK_TO_NOTES ]

GraphQL 一次请求的端到端执行

用一个完整的前后端案例解释 GraphQL Schema 如何成为接口合同,以及客户端任意字段组合如何由执行引擎和 Resolver 在服务端落地。

#synthesis #growing #api #graphql

[!info] related notes

  • 所属 MOC: graphql-moc
  • 相关概念:
  • 易混淆概念:
  • 相关资源:

GraphQL 一次请求的端到端执行

要解决的问题

GraphQL 最容易产生的疑问是:

前端既然可以自由组合字段,难道后端要为每一种组合提前写一个接口吗?

答案是 不需要。

GraphQL 的关键设计是:后端不是预先实现“组合”,而是实现一组 可组合的字段能力;GraphQL 执行引擎根据客户端这一次的 selection set,把需要的字段拼成一棵执行树。

下面用一个简化的 BodySense 风格案例走完整链路。

1. 后端先声明 Schema

页面可能需要用户、最新诊断和当前训练计划:

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

type Diagnosis {
  id: ID!
  summary: String!
}

type TrainingPlan {
  id: ID!
  title: String!
}

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

这份 Schema 表示:

客户端允许从 Query.user 进入
        ↓
拿到 User 后可以继续选择
name / latestDiagnosis / currentTrainingPlan
        ↓
嵌套对象又有自己的可选字段

它只声明 允许查询什么,不说明这些数据具体存在哪里。

2. 后端实现 Resolver

const resolvers = {
  Query: {
    user: (_parent, { id }, ctx) =>
      ctx.userService.getById(id),
  },

  User: {
    latestDiagnosis: (user, _args, ctx) =>
      ctx.diagnosisService.getLatestByUserId(user.id),

    currentTrainingPlan: (user, _args, ctx) =>
      ctx.trainingService.getCurrentByUserId(user.id),
  },
};

这里后端只实现几个稳定能力:

Query.user
User.latestDiagnosis
User.currentTrainingPlan

并没有实现:

user + diagnosis
user + trainingPlan
user + diagnosis + trainingPlan
……

这些组合由客户端查询在运行时组成。

3. 前端声明这次真正需要什么

页面 A 只需要名字:

query GetUserName($id: ID!) {
  user(id: $id) {
    name
  }
}

页面 B 需要名字和诊断:

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

页面 C 再加训练计划:

query GetWorkspace($id: ID!) {
  user(id: $id) {
    name
    latestDiagnosis {
      summary
    }
    currentTrainingPlan {
      title
    }
  }
}

后端不需要因此增加三个 endpoint。

4. 请求怎样发送

GraphQL 常通过一个 HTTP endpoint,例如:

POST /graphql

请求体大致是:

{
  "query": "query GetWorkspace($id: ID!) { ... }",
  "variables": {
    "id": "u1"
  }
}

真正决定“要哪些字段”的不是 URL,而是请求中的 GraphQL operation。

5. 服务端执行引擎做什么

收到 Query 后,GraphQL Server 大致经历:

Parse
  ↓
Validate against Schema
  ↓
Execute selection set
  ↓
call Query.user resolver
  ↓
得到 User
  ↓
只继续解析本次被选中的字段
  ├─ name
  ├─ latestDiagnosis → resolver
  └─ currentTrainingPlan → resolver
  ↓
assemble response

可以把页面 C 看成一棵执行树:

Query.user(id: "u1")
├─ name
├─ latestDiagnosis
│   └─ summary
└─ currentTrainingPlan
    └─ title

GraphQL 执行引擎按照这棵树组织调用和结果。

6. 返回结果

{
  "data": {
    "user": {
      "name": "Sean",
      "latestDiagnosis": {
        "summary": "..."
      },
      "currentTrainingPlan": {
        "title": "Lower Body A"
      }
    }
  }
}

查询形状与响应形状基本对应,因此前端能明确知道自己拿到什么。

为什么“任意组合”能够成立

核心不是后端支持无限种特殊接口,而是:

REST 常见思路
后端定义一组完整 response endpoint

GraphQL 思路
后端定义一组可组合 field contract
           ↓
客户端在允许范围内组合 selection set
           ↓
执行引擎按字段解析

因此“自由”是 Schema 内的组合自由,不是客户端可以随便查询数据库。

Schema 怎样替代传统接口文档的一部分

在 REST 团队里常见:

OpenAPI / Swagger / Apifox 文档
→ 前端查 URL、method、request、response

GraphQL 中,Schema 本身就是机器可读合同:

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

GraphQL 还定义了 introspection,工具可以向服务端查询 Schema 信息。

因此 GraphQL IDE、文档浏览器和 codegen 可以从同一个 Schema 获得:

有哪些 Query / Mutation
有哪些类型和字段
参数是什么类型
哪些字段 nullable
字段之间有哪些关系

常见团队链路是:

Backend Schema
   ↓ publish / introspection / registry
GraphQL tooling
   ├─ API Docs
   ├─ autocomplete
   ├─ validation
   └─ TypeScript codegen
          ↓
Frontend operations

这比“人手维护一份与代码可能漂移的接口表格”更接近 executable contract。

一个非常重要的性能边界

前端只查询:

user { name }

并 不保证数据库只 SELECT name。

如果 userService.getById() 内部仍然执行:

SELECT * FROM users WHERE id = ?;

数据库依旧可能读取更多列,只是 GraphQL 最终不会把未选择字段返回给客户端。

所以:

GraphQL 控制的是 API response selection;数据库查询优化仍然属于 Resolver / Service / Repository 的责任。

同理,嵌套字段还可能造成 N+1,需要 batch、JOIN、DataLoader 等手段治理。

最终心智模型

Schema
= 后端公开能力边界

Operation
= 前端这一次从能力边界中选择什么

Execution Engine
= 根据 selection set 调度字段解析

Resolver
= 一个字段怎样连接到真实业务数据

Service / Repository
= 真正业务规则和数据访问

前端的自由,本质上来自 对稳定字段能力的组合,而不是后端为所有组合提前写代码。

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