如何通过单一URL访问多Spring Boot GraphQL微服务?网关选型与实现
一、Apollo Gateway 是否适用于你的场景?
完全合适。Apollo Gateway是GraphQL联邦(Federation)的官方实现,专门用于整合多个独立的GraphQL服务,对外暴露统一的API入口。它能自动合并各子服务的Schema,根据查询字段自动路由到对应的后端服务,正好匹配你将userlist查询转发到/graphql1、产品相关查询转发到/graphql2的需求。
需要注意的是:Apollo Gateway本身基于Node.js开发,如果你希望网关完全落在Java技术栈里,可以考虑Java生态的替代方案(比如后文提到的Spring GraphQL或Netflix DGS Federation);但如果不介意引入Node.js组件,Apollo Gateway是非常成熟的选择。
二、实现统一GraphQL网关的具体方案
方案1:使用Apollo Gateway(跨技术栈)
子服务配置GraphQL联邦支持
以Spring GraphQL为例,给每个子服务添加联邦依赖:<!-- pom.xml 依赖 --> <dependency> <groupId>com.apollographql.federation</groupId> <artifactId>federation-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency>在User服务的Schema中标记联邦类型:
extend type Query { userlist: [User] } type User @key(fields: "id") { id: ID! name: String! }Product服务同理,定义自己的Query和联邦类型。
搭建Apollo Gateway
创建Node.js项目,安装依赖:npm install @apollo/gateway apollo-server编写网关配置,指定子服务地址:
const { ApolloServer } = require('apollo-server'); const { ApolloGateway } = require('@apollo/gateway'); const gateway = new ApolloGateway({ serviceList: [ { name: 'user-service', url: 'http://localhost:8080/graphql1' }, { name: 'product-service', url: 'http://localhost:8081/graphql2' } ] }); const server = new ApolloServer({ gateway }); server.listen(4000).then(({ url }) => { console.log(`Gateway ready at ${url}`); });启动网关后,访问
http://localhost:4000/graphql即可发起统一查询,网关会自动路由到对应子服务。
方案2:Java技术栈内实现(Spring GraphQL + Gateway)
如果希望网关完全基于Java,推荐Spring生态组合方案:
子服务保持原有配置
确保User和Product服务的Schema清晰划分各自的查询与类型。搭建Spring Gateway + Spring GraphQL网关服务
创建新Spring Boot项目,添加依赖:<!-- pom.xml 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.graphql</groupId> <artifactId>spring-boot-starter-graphql</artifactId> </dependency>自定义Schema合并与路由逻辑:
@Configuration public class GraphQLGatewayConfig { @Bean public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer(RestTemplate restTemplate) { return builder -> builder.schemaFactory((resources, environment) -> { // 拉取子服务Schema并合并 String userSchema = restTemplate.getForObject("http://localhost:8080/graphql1/schema", String.class); String productSchema = restTemplate.getForObject("http://localhost:8081/graphql2/schema", String.class); return SchemaParser.parse(userSchema + "\n" + productSchema); }); } @Bean public RuntimeWiringConfigurer runtimeWiringConfigurer(RestTemplate restTemplate) { return wiringBuilder -> wiringBuilder .type("Query", typeWiring -> typeWiring .dataFetcher("userlist", env -> { // 转发请求到User服务 return restTemplate.postForObject( "http://localhost:8080/graphql1", Map.of("query", "{ userlist { id name } }"), Map.class ).get("data").get("userlist"); }) .dataFetcher("productlist", env -> { // 转发请求到Product服务 return restTemplate.postForObject( "http://localhost:8081/graphql2", Map.of("query", "{ productlist { id name } }"), Map.class ).get("data").get("productlist"); }) ); } }配置Spring Gateway路径映射:
spring: cloud: gateway: routes: - id: graphql-gateway uri: http://localhost:8082 # 网关自身地址 predicates: - Path=/graphql启动网关后,访问
http://localhost:8082/graphql即可统一访问两个子服务的API。
方案3:使用Netflix DGS Federation(全Java栈)
Netflix DGS是Spring生态的GraphQL框架,原生支持联邦特性:
- 子服务添加DGS Federation依赖,在Schema中标记联邦类型;
- 创建DGS Gateway服务,配置子服务地址,网关会自动合并Schema并完成请求路由。
三、关键注意事项
- Schema一致性:避免子服务出现同名类型但字段不一致的情况,联邦模式下可通过
@extends和@key实现类型扩展; - 性能优化:网关层可添加请求缓存、批量请求合并等逻辑,减少子服务重复调用;
- 监控与日志:在网关层统一收集请求日志和监控数据,便于问题排查。
内容的提问来源于stack exchange,提问作者Khyati

