基于graphql-java-tools优化GraphQL端点:无需等待父解析获取请求字段
实现GraphQL字段并行获取(无需等待父解析完成)
我之前也碰到过一模一样的场景——用graphql-java和graphql-java-tools构建的服务,原本父字段(id/title)和子字段(price)是串行调用不同服务,总耗时是两个服务调用的时间之和。要实现并行获取,核心就是利用graphql-java提供的DataFetchingEnvironment提前感知请求的所有字段,然后同时发起所有依赖的服务调用,不用等父对象解析完成再处理子字段。
下面是具体的实现步骤和代码示例:
1. 核心思路:提前识别需要的子字段
在顶层的product查询解析器中,通过DataFetchingEnvironment的选择集(SelectionSet)判断当前请求是否包含offer.price字段。如果包含,就可以直接并行调用服务A(拿id/title)和服务B(拿price),而不是等服务A返回后再触发服务B的调用。
2. 具体代码实现
第一步:修改顶层查询解析器,并行发起服务调用
@Component public class ProductQueryResolver implements GraphQLQueryResolver { private final ServiceA serviceA; private final ServiceB serviceB; public ProductQueryResolver(ServiceA serviceA, ServiceB serviceB) { this.serviceA = serviceA; this.serviceB = serviceB; } public CompletableFuture<ProductResponse> product(String id, DataFetchingEnvironment env) { // 递归检查请求是否需要获取 offer.price 字段 boolean needsOfferPrice = doesRequestNeedOfferPrice(env.getSelectionSet()); // 并行发起两个服务调用:不需要price的话就返回空的已完成Future CompletableFuture<ProductBasicInfo> serviceAFuture = serviceA.fetchProductBasicInfo(id); CompletableFuture<Double> serviceBFuture = needsOfferPrice ? serviceB.fetchProductPrice(id) : CompletableFuture.completedFuture(null); // 等待两个Future完成后,组装最终的ProductResponse return CompletableFuture.allOf(serviceAFuture, serviceBFuture) .thenApply(ignored -> { ProductBasicInfo basicInfo = serviceAFuture.join(); Double price = serviceBFuture.join(); // 将预获取的price存入ProductResponse,供后续子解析器使用 return new ProductResponse(basicInfo.getId(), basicInfo.getTitle(), price); }); } // 递归遍历选择集,判断是否需要offer.price private boolean doesRequestNeedOfferPrice(SelectionSet selectionSet) { for (Selection selection : selectionSet.getSelections()) { if (selection instanceof Field field) { if ("offer".equals(field.getName())) { return field.getSelectionSet().getSelections().stream() .anyMatch(subSel -> subSel instanceof Field subField && "price".equals(subField.getName())); } } } return false; } }
第二步:调整ProductResponse,添加预存price的字段
我们需要把提前获取到的price临时存在ProductResponse中,供子解析器直接使用:
public class ProductResponse { private final String id; private final String title; private final Double preFetchedPrice; // 预获取的price public ProductResponse(String id, String title, Double preFetchedPrice) { this.id = id; this.title = title; this.preFetchedPrice = preFetchedPrice; } // Getter方法 public String getId() { return id; } public String getTitle() { return title; } public Double getPreFetchedPrice() { return preFetchedPrice; } }
第三步:修改子字段解析器,直接使用预存数据
原来的offer解析器需要调用服务B,现在直接用预获取的price即可,无需再发起异步请求:
@Component public class ProductOfferResolver implements GraphQLResolver<ProductResponse> { public OfferResponse offer(ProductResponse product) { // 如果没有预获取price(请求没包含该字段),返回null或空对象 if (product.getPreFetchedPrice() == null) { return null; } return new OfferResponse(product.getPreFetchedPrice()); } } // OfferResponse类保持不变 public class OfferResponse { private final Double price; public OfferResponse(Double price) { this.price = price; } public Double getPrice() { return price; } }
3. 关键注意点
- 选择集解析的严谨性:如果请求中使用了别名、嵌套片段或者更复杂的字段结构,需要调整
doesRequestNeedOfferPrice方法的逻辑,确保准确识别是否需要子字段。 - 异常处理:可以给每个
CompletableFuture添加exceptionally回调,处理单个服务调用失败的情况(比如服务A失败但服务B成功,或者反之),避免整个请求直接报错。 - 扩展性:如果后续有更多子字段需要并行获取,只需要在顶层解析器中添加对应的服务调用判断,保持并行逻辑即可。
内容的提问来源于stack exchange,提问作者Wyko
相关产品推荐
相关产品推荐

