使用Apollo Federation与graphql-java-kickstart执行查询时遇运行时错误
我有两个基于graphql-java-kickstart(非SpringBoot)的后端服务,分别提供accounts和billing的GraphQL数据,希望在其上部署Apollo Federation Router。请问是否可以将apollo-federation-jvm与graphql-java-kickstart结合使用?目前看到的联邦Schema示例多为SpringBoot相关。
我已完成相关实现并成功启动Apollo Router,但执行查询时出现以下错误:
"message": "Variable 'representations' has an invalid value: Variable 'representations' has coerced Null value for NonNull type '[_Any!]!'"
该错误发生在获取账户账单的如下查询中:
query($representations:[_Any!]!){_entities(representations:$representations){...on AccountsItem{billing{accountNumber billingDetails{lastBillAmount}}}}}
Accounts服务Schema
type Query { accounts: [AccountsItem!]! searchAccounts(filter: AccountFilter!): [AccountsItem!] } input AccountFilter { accountAlias: String } "AccountOverview for account list call" type AccountsItem @key(fields: "alias"){ accountNumber: String alias: String status: String cbpid: String accountTypeNumber: String autoPayPromoEligInd: String }
Billing服务Schema
type Query { billing(accountAlias: String!): Billing! } type AccountsItem @key(fields: "alias") @extends { alias: String @external billing: Billing } type Billing { accountNumber: String billingDetails: BillingDetails } type BillingDetails{ billType: String currentBalance: String lastBillAmount: String }
实现代码
实体数据获取器
private static DataFetcher<?> getDataFetcher(GraphQLResolver<?>[] resolvers) { DataFetcher<?> entityDataFetcher = env -> { List<Map<String, Object>> argument = env.getArgument(_Entity.argumentName); return argument.stream() .map(reference -> { if ("AccountsItem".equals(reference.get("__typename"))) { return new AccountsItem((String)reference.get("alias")); } else if ("billing".equals(reference.get("__typename"))) { return ((BillingResolver)resolvers[0]).billing((String)reference.get("alias"), env); } return null; }) .collect(Collectors.toList()); }; return entityDataFetcher; }
GraphQL实例构建
@Bean public GraphQL graphQL(GraphQLResolver<?>[] resolvers) { final String originalSDL = readResource("graphql/schema.graphqls"); DataFetcher<?> entityDataFetcher = getDataFetcher(resolvers); GraphQLSchema federatedSchema = Federation.transform(originalSDL, getRuntimeWiring(entityDataFetcher)) .fetchEntities(entityDataFetcher) .resolveEntityType(env -> { final Object src = env.getObject(); if (src instanceof AccountsItem) { return env.getSchema().getObjectType("AccountsItem"); } return null; } ) .build(); return GraphQL.newGraphQL(federatedSchema).build(); } private static RuntimeWiring getRuntimeWiring(DataFetcher<?> entityDataFetcher){ return RuntimeWiring.newRuntimeWiring() .type("AccountsItem",typeWiring -> typeWiring .dataFetcher("billing", entityDataFetcher)) .type("Query",typeWiring -> typeWiring .dataFetcher("billing", entityDataFetcher)) .build(); }
请求协助排查该错误原因并解决。
关于apollo-federation-jvm与graphql-java-kickstart的兼容性
完全可以结合使用,两者都是基于graphql-java生态的组件,只要正确处理Schema联邦转换和数据获取逻辑即可,并非只能在SpringBoot环境下使用。
错误原因排查与修复
1. 核心问题定位
错误提示representations变量被强制转换为Null,本质是代码中数据获取器和RuntimeWiring的配置错误,导致服务无法正确处理Apollo Router传递的联邦查询请求,最终返回不符合要求的Null值触发校验错误。
2. 代码中的具体问题
(1)getDataFetcher类型判断错误
Billing服务中扩展的是AccountsItem类型,Apollo Router传递的__typename应为"AccountsItem",但代码中错误判断为"billing",导致匹配失败返回Null。
(2)RuntimeWiring配置错误
将用于处理_entities查询的entityDataFetcher绑定给了AccountsItem的billing字段,该数据获取器的逻辑并不适用于解析单个字段,导致字段解析异常。
(3)resolveEntityType逻辑不完整
缺少对Billing类型的处理,后续可能引发其他类型解析问题。
3. 修复后的代码
修正getDataFetcher(Billing服务)
private static DataFetcher<?> getDataFetcher(GraphQLResolver<?>[] resolvers) { DataFetcher<?> entityDataFetcher = env -> { List<Map<String, Object>> argument = env.getArgument(_Entity.argumentName); return argument.stream() .map(reference -> { // 仅处理AccountsItem类型,因为Billing服务扩展的是该类型 if ("AccountsItem".equals(reference.get("__typename"))) { AccountsItem item = new AccountsItem(); item.setAlias((String) reference.get("alias")); return item; } return null; }) .collect(Collectors.toList()); }; return entityDataFetcher; }
修正RuntimeWiring配置
private static RuntimeWiring getRuntimeWiring(GraphQLResolver<?>[] resolvers) { BillingResolver billingResolver = (BillingResolver) resolvers[0]; return RuntimeWiring.newRuntimeWiring() // 给AccountsItem的billing字段绑定专属解析器 .type("AccountsItem", typeWiring -> typeWiring .dataFetcher("billing", env -> { AccountsItem item = env.getSource(); return billingResolver.billing(item.getAlias(), env); })) // 给Query的billing字段绑定正常解析逻辑 .type("Query", typeWiring -> typeWiring .dataFetcher("billing", env -> { String accountAlias = env.getArgument("accountAlias"); return billingResolver.billing(accountAlias, env); })) .build(); }
完善resolveEntityType(可选)
.resolveEntityType(env -> { final Object src = env.getObject(); if (src instanceof AccountsItem) { return env.getSchema().getObjectType("AccountsItem"); } if (src instanceof Billing) { return env.getSchema().getObjectType("Billing"); } return null; })
4. 额外检查点
- 确保Accounts服务的
_entities查询能正确返回包含alias的AccountsItem对象。 - 检查Apollo Router配置,确认它已正确发现两个服务的联邦Schema。
- 验证联邦查询流程:先从Accounts服务获取
AccountsItem,再由Router调用Billing服务关联获取billing数据。
内容的提问来源于stack exchange,提问作者suraj bahl

