2. GraphQL 基础回顾:核心概念与类型系统

在深入 BFF 层的请求合并与数据加载优化之前,必须牢固掌握 GraphQL 的核心概念。本章将系统回顾 Schema、Query、Mutation、Subscription 四大支柱,以及类型系统与查询语法,为后续的优化策略打下坚实的理论基础。

2.1 GraphQL 核心概念全景

GraphQL 是一种用于 API 的查询语言,由 Facebook 于 2015 年开源。它让客户端能够精确地声明所需的数据结构,从而解决 RESTful API 中常见的“过度获取”和“获取不足”问题。其核心由以下四个概念构成:

  • Schema(模式):API 的契约与类型定义文件,描述了所有可用的数据对象、字段及其关系。
  • Query(查询):用于从服务端读取数据,类似于 REST 中的 GET 请求。
  • Mutation(变更):用于修改服务端数据(增、删、改),类似于 REST 中的 POST/PUT/DELETE。
  • Subscription(订阅):用于建立长连接,实时接收服务端推送的数据更新。
关键认知: 在 BFF 层设计中,Query 是请求合并优化的主战场,而 Schema 的设计质量直接决定了数据加载的效率。理解这三者的关系是优化工作的起点。
GraphQL 核心概念关系图 GraphQL Schema Query (读取) Mutation (写入) Subscription (实时) Schema 是 API 的契约,定义了 Query、Mutation、Subscription 的入口与类型 客户端只能请求 Schema 中声明的字段,服务端按契约执行解析

2.2 类型系统:GraphQL 的基石

GraphQL 拥有强类型系统,每个字段都必须声明其返回类型。类型系统是 Schema 的核心,也是实现数据加载优化的基础——因为类型决定了数据如何被解析和组合。

2.2.1 标量类型与对象类型

类型类别 具体类型 说明 BFF 优化关注点
标量类型 Int, Float, String, Boolean, ID 叶子节点,不可再拆分 直接返回,无需进一步解析
对象类型 type User { ... } 包含多个字段的复合类型 需要 Resolver 逐字段解析,是 DataLoader 优化的主要目标
枚举类型 enum Status { ACTIVE INACTIVE } 限定字段的取值范围 减少无效数据传递
列表类型 [User] 表示一组对象 批量加载的典型场景,N+1 问题高发区
非空类型 String! 字段值不能为 null 减少客户端空值判断逻辑

2.2.2 接口与联合类型

在 BFF 层,接口(Interface)和联合类型(Union)常用于处理多态数据,例如“搜索结果”可能包含用户、文章、商品三种类型。

# 接口定义
interface SearchResult {
  id: ID!
  title: String!
}

type User implements SearchResult {
  id: ID!
  title: String!
  avatar: String
}

type Article implements SearchResult {
  id: ID!
  title: String!
  content: String
}

# 联合类型
union MediaItem = Image | Video

type Query {
  search(keyword: String!): [SearchResult!]!
  mediaLibrary: [MediaItem!]!
}
优化陷阱: 联合类型和接口在解析时通常需要额外的类型判断逻辑(__typename),这会增加 Resolver 的复杂度。在 BFF 层,如果多态场景不复杂,建议优先使用对象类型加可选字段,以减少解析开销。

2.3 Query 与 Mutation 的查询语法

GraphQL 的查询语法极其灵活,客户端可以精确控制返回的数据结构。理解查询语法是后续实现“请求合并”的前提——因为合并的本质是将多个查询片段组合成一个高效的查询树。

2.3.1 基础查询与参数传递

# 查询单个用户,带参数
query GetUser($userId: ID!) {
  user(id: $userId) {
    id
    name
    email
    profile {
      age
      city
    }
  }
}

# 查询变量
# 变量:{ "userId": "123" }

2.3.2 别名与片段:请求合并的语法基础

别名(Alias) 允许在同一个查询中请求同一个字段多次,片段(Fragment) 允许复用字段集合。这两者是 BFF 层实现查询合并的核心语法工具。

# 使用别名同时查询两个用户
query GetTwoUsers {
  user1: user(id: "1") {
    ...UserFields
  }
  user2: user(id: "2") {
    ...UserFields
  }
}

# 定义片段,复用字段
fragment UserFields on User {
  id
  name
  email
  profile {
    age
    city
  }
}

2.3.3 Mutation 的典型写法

# 变更操作,通常包含输入类型和返回类型
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    createdAt
    author {
      id
      name
    }
  }
}

# 输入变量
# { "input": { "title": "GraphQL 优化", "content": "..." } }

2.4 Subscription:实时数据推送

Subscription 使用 WebSocket 等持久连接协议,在 BFF 层常用于实时通知、数据变更推送等场景。其语法与 Query 类似,但使用 subscription 关键字。

subscription OnPostCreated {
  postCreated {
    id
    title
    author {
      name
    }
  }
}
BFF 层实践要点: Subscription 在 BFF 层通常作为“代理”存在——BFF 订阅下游微服务的事件,然后推送给前端。这要求 BFF 层具备连接管理和事件路由能力,是请求合并之外的另一大优化维度。
查询语法与请求合并关系图 客户端查询 query { user1: user(id: "1") { ...UserFields } user2: user(id: "2") { ...UserFields } } 别名 + 片段 BFF 层解析引擎 1. 解析查询 AST 2. 识别重复字段 3. 合并相同类型请求 4. 批量加载数据 5. 按别名分发结果 DataLoader 批量优化 下游数据源 User Service Post Service Database 批量查询接口

2.5 本章小结