ABP VNext + GraphQL Federation:跨微服务联合 Schema 分层 🚀
在微服务架构下,服务之间往往需要相互通信,而 GraphQL Federation 提供了一个有效的解决方案,帮助我们将多个微服务的 GraphQL API 聚合成一个统一的入口。在这篇文章中,我们将展示如何使用 ABP VNext 和 GraphQL Federation 实现跨微服务联合 Schema 分层,从而解耦服务,提高可维护性和扩展性。
📚 目录
1. 引言 ✨
TL;DR
- 基于 HotChocolate Federation,将多个 ABP 微服务的 GraphQL API 组合成统一入口 🌐
- 服务间通过跨服务 Schema 联合,避免紧耦合与多端 API 重复 🚀
- 演示如何在多服务架构中,使用
@key
和@external
实现跨服务查询和扩展 🔗 - 解决微服务之间数据传递问题,支持服务解耦与动态扩展 🌱
在微服务架构中,前端往往需要从多个微服务获取数据,这导致了前端需要处理多个 API 请求并进行复杂的聚合。而 GraphQL Federation 为这一问题提供了解决方案。通过 GraphQL Federation,我们可以将多个微服务的 GraphQL API 聚合成一个统一的入口,从而简化前端的请求和聚合逻辑,同时保持微服务的解耦和独立性。
2. 环境与依赖 ⚙️
在开始之前,我们需要配置一些基本环境和依赖项:
🛠️ 平台版本
- .NET 7/8
- ABP VNext 7.x/8.x
🔗 NuGet 包
HotChocolate.AspNetCore
HotChocolate.AspNetCore.Federation
Volo.Abp.AspNetCore.Mvc
(ABP WebAPI 集成)
🔧 可选组件
- Redis:用于共享缓存或跨服务会话管理(可选)。
3. GraphQL Federation 基础 🔎
3.1 什么是 GraphQL Federation?
GraphQL Federation 是一种通过跨服务联合模式,将多个 GraphQL 服务组合成统一的 API 图。每个微服务负责自己的部分 Schema,它们通过指定的标注如 @key
和 @external
来共享和扩展数据,从而实现跨服务的数据查询。
- @key:用于标识联合查询的主字段。
- @external:用于引用其他服务的数据字段。
3.2 典型服务场景 🏗️
假设我们有三个微服务:订单服务、客户服务、产品服务。在这些服务中,我们需要联合查询客户和产品信息,同时确保各个服务之间保持独立。
4. 配置 ABP 服务的 GraphQL Schema 🔧
4.1 启用 GraphQL
首先,我们需要在 ABP 模块中配置 GraphQL 服务,并启用 Federation 特性。以下是如何在 Startup.cs
中配置 GraphQL:
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddFederation(); // 使能 Federation 特性
}
}
4.2 定义 @key
和 @external
在微服务的 GraphQL Schema 中,我们使用 @key
和 @external
来定义跨服务的数据联合。
4.2.1 定义 @key
(联合查询主字段)
[Key("id")]
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
4.2.2 定义 @external
(跨服务引用)
public class Product
{
public int Id { get; set; }
[External] public int CustomerId { get; set; } // 来自于 Customer 服务
}
4.3 微服务的 Query 类型定义
对于每个微服务,我们都需要定义相应的 Query
类型。以下是 订单服务 的 Query
类型定义:
4.3.1 Order Service Schema
public class Query
{
private readonly IOrderRepository _orderRepository;
public Query(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public IQueryable<Order> GetOrders() => _orderRepository.AsQueryable();
}
5. GraphQL 联合查询与服务解耦 🔄
5.1 跨微服务查询
通过 GraphQL Federation,我们可以在多个微服务之间进行联合查询。以下是一个联合查询的例子,查询来自 客户服务 和 产品服务 的数据:
query {
customer(id: 1) {
id
name
}
product(id: 2) {
id
name
customerId
}
}
5.2 实现分布式查询与联接
我们可以在 GraphQL 层将来自不同服务的数据进行联合查询。例如,将 订单信息 和 产品信息 联接,跨多个服务聚合数据。
query {
order(id: 1) {
id
customerId
productId
}
product(id: 1) {
name
price
}
}
5.3 示例查询
以下是查询多个微服务数据的完整示例:
query {
customer(id: "1") {
name
email
}
product(id: "1001") {
name
description
}
}
6. 微服务间数据扩展与版本控制 🔧
6.1 扩展类型
为了实现跨服务的数据扩展,我们可以通过 @extend
装饰器在不同服务间进行数据扩展。例如,扩展 产品服务 以获取 客户信息:
[ExtendObjectType("Product")]
public class ProductCustomerExtension
{
private readonly ICustomerRepository _customerRepository;
public ProductCustomerExtension(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public Customer Customer([Parent] Product product) => _customerRepository.GetById(product.CustomerId);
}
6.2 版本管理
随着服务的发展,我们可能需要扩展和版本化 GraphQL Schema。每个微服务都可以独立演进其 Schema,保持与其他服务的兼容性。
Schema 合并与版本控制
每个微服务独立演进 GraphQL Schema,保持与其他服务的兼容性。服务的版本可以通过 @key
和 @external
标记的字段实现向后兼容。对于新版本服务,前后端可以通过合并新 Schema 来扩展功能。
extend type Query {
newCustomer(id: Int!): Customer
}
7. 安全性与权限管理 🔐
7.1 服务级授权
通过 GraphQL 中的 @auth
装饰器管理每个字段或查询的权限控制。结合 ABP 的多租户授权管理,使用 ABP 的权限和角色系统控制跨服务查询权限。
type Query {
@auth(roles: ["admin"])
getUser(id: ID!): User
}
7.2 API 网关与流量控制
使用 Ocelot 或 YARP 配合 ABP 实现微服务层的统一授权、认证和流量控制。
{
"ReRoutes": [
{
"UpstreamPathTemplate": "/api/order/**",
"DownstreamPathTemplate": "/order/**",
"UpstreamHttpMethod": [ "GET", "POST" ]
}
]
}
8. Kibana 监控与性能优化 📊
8.1 结合 Elastic APM
我们可以通过集成 Elastic APM 监控跨服务的 GraphQL 查询,采集服务性能数据,监控每个 GraphQL 查询的响应时间、吞吐量和错误率。
详细可参见我的另一篇技术博客:ABP VNext + Elastic APM:微服务性能监控
8.2 性能优化
通过分析服务的性能数据,优化查询响应时间和吞吐量,确保系统的高性能和高可用。
{
"metrics": {
"responseTime": 100,
"throughput": 5000,
"errorRate": 0.02
}
}
9. 实践演示 🎯
9.1 准备项目
用 ABP CLI(或 dotnet CLI + ABP 模板)创建 4 个项目:
# 安装 ABP CLI
dotnet tool install Volo.Abp.Cli -g
# 创建微服务模板
abp new CustomerService -t app -u none --tiered
abp new ProductService -t app -u none --tiered
abp new OrderService -t app -u none --tiered
# 创建 Gateway 项目,用作 Federation 聚合层
abp new ApiGateway -t app -u none --tiered
目录结构示例:
/solutions
/CustomerService
/ProductService
/OrderService
/ApiGateway
9.2 启动微服务
在各服务的 appsettings.json
中,按需开启 Elastic APM:
// CustomerService/appsettings.json
{
"ElasticApm": {
"ServerUrls": "http://localhost:8200",
"ServiceName": "CustomerService",
"Environment": "dev"
}
}
然后分别在 5001、5002、5003 端口启动:
cd CustomerService && dotnet run --urls "http://localhost:5001"
cd ProductService && dotnet run --urls "http://localhost:5002"
cd OrderService && dotnet run --urls "http://localhost:5003"
9.3 在 Gateway 中配置 Federation
在 ApiGateway
的 Startup.cs
中,像这样注册子图:
public class ApiGatewayModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddGraphQLServer()
.AddRemoteSchema("customer", c => c.Http("http://localhost:5001/graphql"))
.AddRemoteSchema("product", c => c.Http("http://localhost:5002/graphql"))
.AddRemoteSchema("order", c => c.Http("http://localhost:5003/graphql"))
.AddTypeExtensionsFromFile("./SchemaExtensions.graphql")
.AddApolloFederation();
}
}
SchemaExtensions.graphql
可以包含跨图的扩展定义:
extend type Query {
customer(id: Int!): Customer @delegate(schema: "customer", path: "customerById(id: $id)")
product(id: Int!): Product @delegate(schema: "product", path: "productById(id: $id)")
}
9.4 执行联合查询
启动 Gateway(默认 http://localhost:5000/graphql
),打开 GraphQL Playground,运行:
query {
customer(id: 1) {
id
name
orders { # 这里 orders 来自 OrderService 的扩展
id
total
}
}
product(id: 2) {
id
name
customer { # 来自 ProductService -> CustomerService 的扩展
name
}
}
}
9.5 Kibana & Elastic APM 监控
- 在 Elasticsearch/Kibana 中创建 APM 应用,监听
CustomerService
、ProductService
、OrderService
、ApiGateway
服务。 - 在 Kibana APM 界面查看分布式 Trace,过滤 URI 包含
/graphql
的请求。 - 分析每次联合查询中各服务的响应时间和错误率,并根据查询热度添加 Redis DataLoader 或缓存。
9.6 性能优化建议
- 分页/过滤:对
.GetOrders()
添加分页参数,避免一次性拉取全部数据。 - DataLoader:在 GraphQL Resolver 中使用 DataLoader 批量加载跨服务数据,减少子请求数量。
- 缓存:对高频查询结果在 Redis 中缓存,并结合 APM 监控命中率。
- 熔断/重试:使用 Polly 实现服务间 HTTP 调用的熔断和重试,提升可用性。