基于Apollo的远程Schema拼接:如何扩展类型实现跨服务关联?
我来帮你搞定这个远程Schema拼接里的类型关联问题!你已经写对了扩展类型的SDL部分,接下来核心就是补全resolvers里的字段 resolver,让这两个分散在不同服务的类型能互相关联起来。
你需要在resolvers对象里定义User.profile和Profile.user这两个字段的 resolver 函数,它们的作用是把请求转发到对应的远程服务,获取关联数据。
假设你的userSchema来自用户服务(User有id字段),profileSchema来自资料服务(Profile有userId字段),具体代码如下:
const resolvers = { User: { // 从当前User对象获取对应的Profile async profile(parent, args, context, info) { // parent就是当前返回的User实例,包含它的唯一标识id return info.mergeInfo.delegateToSchema({ schema: profileSchema, operation: 'query', fieldName: 'profileByUserId', // 要求Profile服务提供这个查询字段 args: { userId: parent.id, }, context, info, }); }, }, Profile: { // 从当前Profile对象获取对应的User async user(parent, args, context, info) { // parent是当前返回的Profile实例,包含关联的userId return info.mergeInfo.delegateToSchema({ schema: userSchema, operation: 'query', fieldName: 'userById', // 要求User服务提供这个查询字段 args: { id: parent.userId, }, context, info, }); }, }, };
这里的关键是info.mergeInfo.delegateToSchema方法——它会帮你自动处理远程服务的请求转发,不用自己手动写HTTP调用或者客户端逻辑,Apollo会封装好这部分工作。
要让上面的resolver生效,你的两个远程服务必须提供对应的单条/批量查询字段:
- 用户服务需要有
userById(id: ID!): User这样的查询,根据id返回单个用户 - 资料服务需要有
profileByUserId(userId: ID!): Profile这样的查询,根据userId返回对应的资料
如果远程服务没有这些字段,得先在对应的服务里添加,不然关联逻辑会因为找不到数据源而失败。
配置完成后,你可以用GraphQL Playground测试关联查询,比如:
# 查询用户及其关联的资料 query GetUserWithProfile { userById(id: "1001") { id username profile { bio avatarUrl } } }
或者反向查询:
# 查询资料及其关联的用户 query GetProfileWithUser { profileByUserId(userId: "1001") { bio user { id username } } }
如果能正常返回嵌套的关联数据,就说明你的类型关联配置成功了。
如果你的业务场景需要批量查询多个User(比如users(limit: 10)),默认的resolver会发起10次单独的Profile服务请求,造成N+1性能问题。这时候可以用DataLoader来批量处理:
// 先创建DataLoader实例,批量获取资料 const profileLoader = new DataLoader(async (userIds) => { const profiles = await info.mergeInfo.delegateToSchema({ schema: profileSchema, operation: 'query', fieldName: 'profilesByUserIds', // 要求Profile服务提供批量查询字段 args: { userIds }, context, info, }); // 按传入的userIds顺序映射结果,保证返回顺序和请求一致 return userIds.map(id => profiles.find(p => p.userId === id)); }); // 修改User.profile的resolver,使用loader批量获取 User: { profile(parent) { return profileLoader.load(parent.id); } }
这样就能把N次请求合并成1次,大幅提升Gateway的性能。
内容的提问来源于stack exchange,提问作者GaryDevenay

