如何在Apollo嵌套查询中高效跨子图传递用户数据至折扣服务
问题:Apollo联邦中高效为discounts-service传递用户数据的方案
背景
现有联邦GraphQL Schema如下:
type Query { products: [Product]! } // resolved by `discounts-service` type Discount { priceWithDiscount: Float expired: Boolean } // resolved by `pricing-service` type Price @key(fields: "sku") @extends { sku: String! amount: Int! discount: Discount! @requires(fields: "amount") } // resolved by `products-service` type Product @key(fields: "sku") { sku: String! title: String! price: Price! }
查询逻辑为:返回多个Product,每个Product包含Price节点,Price又关联Discount节点。
现在需要让Discount与单个用户绑定,其解析器需要获取当前查询用户的id和segmentation属性,用户节点定义如下:
// resolved by `users-service` type User @key(fields: "id") { id: String! name: String! segmentation: String! // .... }
要求实现高效的方案:避免在Product/Price节点添加@external类型User这类朴素方案(会导致Apollo Router为每个产品重复调用users-service,造成资源浪费)。同时,也不采用给discount加参数的方案——该方案需要调用方先获取用户数据,且客户端传入的信息不可信(用户可能伪造segmentation)。
可行高效方案
1. 通过请求上下文(Context)传递用户身份信息
这是最直接且安全的方案:
- 在**Apollo Router(网关)**层面完成用户身份解析:比如从请求头的JWT、Session等可信来源提取用户
id和segmentation,将这些信息注入到每个子服务的请求上下文中。 discounts-service的Discount解析器直接从请求上下文中读取用户数据,无需通过Schema层级传递,也不会触发对users-service的重复调用。- 由于数据由网关解析后注入,客户端无法篡改,保证了数据可信度。
示例代码(伪代码):
- 网关配置中处理认证并注入上下文:
// Apollo Router 自定义插件逻辑 export function plugin() { return { async requestDidStart() { return { async didResolveOperation({ request, context }) { // 从请求头提取JWT并解析 const token = request.http.headers.get('Authorization')?.split(' ')[1]; const userInfo = verifyToken(token); // 解析出id和segmentation // 将用户信息注入到共享上下文 context.user = { id: userInfo.id, segmentation: userInfo.segmentation }; } }; } }; }
- discounts-service的解析器读取上下文:
// discounts-service中Discount的解析器 const resolvers = { Price: { discount(parent, args, context) { const { id, segmentation } = context.user; const originalAmount = parent.amount; // 根据用户信息计算折扣 return calculateDiscount(originalAmount, id, segmentation); } } };
2. 基于联邦共享上下文的批量用户数据获取
如果用户信息无法直接从网关请求头解析,需要调用users-service获取,可采用此方案:
- 在网关层面,针对当前查询**仅调用一次
users-service**获取当前用户的完整数据,将其存入联邦共享上下文。 - 后续所有子服务(包括
discounts-service)都能从共享上下文中直接读取用户数据,无需重复调用users-service,无论返回多少Product,都只会触发一次用户数据查询。
这种方式既保证了效率,又确保用户数据来自可信服务,避免客户端伪造。
内容的提问来源于stack exchange,提问作者voyager11
相关产品推荐
相关产品推荐

