微服务联邦与企业级数据图


微服务联邦与企业级数据图

为什么需要联邦

一个中型到大型企业通常有几十个微服务,每个服务有自己的数据库和数据域。前端应用需要的数据往往跨越多个服务的边界——一个用户详情页需要从用户服务、订单服务、支付服务、通知服务获取数据。

在REST架构中,这通常意味着前端需要调用多个API端点并自己组装数据,或者在后端引入一个BFF(Backend for Frontend)层来做聚合。

GraphQL Federation提供了第三种选择:每个微服务继续做自己的事,通过联邦组成一个统一的数据图,让客户端用一次查询获取所有需要的数据。

联邦V2的核心概念

子图(Subgraph):每个微服务定义自己的GraphQL Schema,只包含自己拥有的类型和字段。

实体(Entity):跨服务共享的数据单元,用 @key 标识。实体在哪个服务定义基础字段,其他服务可以 extend 这个实体来添加自己管理的字段。

网关(Gateway):接收客户端查询,根据Supergraph的查询计划分解到各子图,组装结果。

# 用户子图 — 定义User实体 type User @key(fields: "id") { id: ID! name: String! email: String! } # 订单子图 — 扩展User实体,添加订单相关字段 extend type User @key(fields: "id") { orders(status: OrderStatus): [Order!]! } type Order @key(fields: "id") { id: ID! status: OrderStatus! total: Float! userId: ID! } # 支付子图 — 扩展Order实体 extend type Order @key(fields: "id") { paymentMethod: String lastPaymentAt: DateTime } ​

图:企业级Supergraph架构

图:企业级Supergraph架构

Supergraph的构建和部署

构建Supergraph的流程:

# 1. 每个子图团队用Rover CLI注册自己的Schema rover subgraph publish my-app@prod \ --name users \ --schema ./users/schema.graphql \ --routing-url http://users-service:4001/graphql rover subgraph publish my-app@prod \ --name products \ --schema ./products/schema.graphql \ --routing-url http://products-service:4002/graphql # 2. 网关从注册中心拉取Supergraph # 3. 客户端查询到达网关,查询计划器分解查询到各子图 ​

Apollo Rover CLI是联邦管理的核心工具。它处理Schema注册、Supergraph构建、兼容性检查等工作。

查询计划的执行

当一个涉及多个子图的查询到达网关:

{ user(id: "1001") { name # → 用户子图 orders { # → 订单子图 total # → 订单子图 items { # → 订单子图 product { # → 商品子图 name price } } } notifications { # → 通知子图 message read } } } ​

网关的查询计划器分析查询,生成执行计划:

  1. 先查用户子图获取用户基本信息(name)
  2. 并行查订单子图和通知子图
  3. 订单子图返回后,查商品子图获取产品信息
  4. 组装所有结果

这个执行计划考虑了数据依赖和并行性,在保证正确性的前提下最大化并发执行。

实体解析的细节

当订单子图需要返回 items.product 时,它自己没有产品数据。网关发现 Product 是商品子图定义的实体,于是把 product.id 列表发送给商品子图的 __resolveReference 解析器,获取完整的产品数据。

这个过程叫做查询分解与实体解析(Query Decomposition and Entity Resolution),是联邦的核心机制。

构建企业级数据图的关键在于:每个子图只管理自己负责的数据域,不试图做别人的事。这种边界清晰度是联邦架构的可维护性的基础。

实体键的设计

@key 是联邦的"外键",它的设计直接决定查询计划的效率。单一 id 是最简单的键,但业务实体常常需要复合键:

# 多租户场景:同一 id 在不同租户下是不同实体 type Order @key(fields: "tenantId id") { tenantId: ID! id: ID! total: Float! } ​

复合键要求每个子图都返回完整的键字段,否则网关无法对齐实体。另一个常见设计是把业务键(订单号、sku)与内部主键分开:内部主键做 @key 保证稳定,业务键暴露为普通字段。键字段一旦定下来就很难改,因为它被所有子图的实体解析器引用,改动等于一次跨服务重构——所以实体键要在联邦建设初期就认真设计。

字段所有权冲突

联邦最常见的治理问题:两个子图都声明了同一个实体的同一个字段。网关无法决定听谁的,会在组合时报错。解决方式有三种:

  1. @shareable 声明共享:字段值在所有子图一致,网关任选一个来源。
  2. 明确归属:字段只在一个子图定义,其余子图需要时用 @external 引用。
  3. 上移到独立子图:把公共字段抽到一个基础数据子图,其他子图引用。

实践上,团队容易掉进"谁都能改 User"的坑。建议在组织层面约定:每个实体字段只有一个 owner,owner 之外的子图想加字段,要么 extend 出新字段(不冲突),要么通过 @requires 计算派生字段(依赖已有字段)。这条约定比任何工具都能减少联邦冲突。

联邦的故障模式

联邦引入网关后,故障面也增加了:

  • 子图宕机:网关无法解析该子图的字段。提前用查询计划检查哪些查询会受影响,配合客户端降级。
  • 实体解析超时:跨子图请求链变长,总耗时是各段之和。为子图请求设置超时和重试上限,避免一个慢服务拖垮整个网关。
  • 版本错配:子图 Schema 与 Supergraph 不一致,组合时出现未定义字段。发布流程里强制先 check 再 publish,是唯一的可靠防线。

理解了这些故障模式,才能为联邦架构设计出对应的监控与告警,而不是等服务出问题再救火。

图:查询计划执行过程

图:查询计划执行过程

作者与出处
原作者:灏天文库
来源:平台策划编纂
整理人: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U