🚀 从单体到微服务:基于 ABP vNext 模块化设计的演进之路
🧩 引言
在需求多变且性能压力日益增大的背景下,传统单体应用在部署、维护和扩展方面存在显著挑战。
ABP vNext 作为基于 ASP.NET Core 的框架,自带模块化设计支持,方便开发者实现从单体到微服务的平滑转换。
本文通过一个简单项目,展示如何使用 ABP vNext 实现服务化设计,包括模块划分、远程调用、API 网关配置、CAP 分布式事务等。
🏗️ 架构演进概览
单体架构
在单体架构中,所有功能模块集成在同一个项目中,模块之间通过类方法调用,部署和维护相对简单。
微服务架构
系统被拆分成多个独立模块服务,通过 HTTP API 或消息队列通信,展示更好的扩展性和维护性能。
🧱 模块划分与项目结构
在 ABP vNext 中,推荐根据 DDD 思想划分模块,每个模块包含:
- Domain.Shared:定义公共定义
- Domain:实体、仓库、基础进程
- Application.Contracts:应用服务接口
- Application:应用服务实现
- HttpApi:Controller层
- HttpApi.Client:远程调用代理
- EntityFrameworkCore:数据库配置
示例项目目录结构:
├── modules
│ ├── Product
│ │ ├── Product.Domain.Shared
│ │ ├── Product.Domain
│ │ ├── Product.Application.Contracts
│ │ ├── Product.Application
│ │ ├── Product.HttpApi
│ │ ├── Product.HttpApi.Client
│ │ └── Product.EntityFrameworkCore
│ ├── Order
│ │ ├── Order.Domain.Shared
│ │ ├── Order.Domain
│ │ ├── Order.Application.Contracts
│ │ ├── Order.Application
│ │ ├── Order.HttpApi
│ │ ├── Order.HttpApi.Client
│ │ └── Order.EntityFrameworkCore
│ └── Customer
│ ├── Customer.Domain.Shared
│ ├── Customer.Domain
│ ├── Customer.Application.Contracts
│ ├── Customer.Application
│ ├── Customer.HttpApi
│ ├── Customer.HttpApi.Client
│ └── Customer.EntityFrameworkCore
├── gateways
│ └── ApiGateway
├── services
│ ├── IdentityService
│ └── AdministrationService
└── shared
└── Shared.Hosting
💡 远程调用方案
以下示例展示两种远程调用方案,任选其一即可,不必同时使用。
特性 | 手写防腐层(ProductServiceClient ) | ABP HTTP 客户端代理(AddHttpClientProxies ) |
---|---|---|
实现成本 | 需手动编写接口、实现类和异常包装;工作量大 | 一行注册代码,自动生成所有 RPC 接口的代理,零手写 |
依赖注入 | 需显式注册:AddTransient<IProductServiceClient, ProductServiceClient>() | 直接注入业务接口:IProductAppService |
维护与升级 | 接口或地址变更时需手动修改,多点维护 | 修改 RemoteServices 配置即可全局生效 |
序列化与配置 | 需手动配置 HttpClient 、序列化、超时等 | 开箱即用,内置序列化、重试和标头继承 |
异常与重试策略 | 需自行捕获异常并实现重试/熔断 | 可集中配置 Polly 策略,代码更简洁 |
性能与一致性 | 可针对单接口优化,但全局策略不统一 | 统一客户端池复用,保证跨接口一致性 |
典型场景 | 接口少、调用简单、需自定义逻辑 | 接口多、需统一治理、微服务大规模场景 |
建议
- 若仅少量跨模块调用或需自定义特殊逻辑,可使用手写防腐层;
- 对于微服务化项目、接口众多或需统一鉴权/重试策略,推荐使用
AddHttpClientProxies
自动代理方案。
🔗 服务通信与防腐层设计
以 Order 服务调用 Product 服务为例:
IProductServiceClient.cs
public interface IProductServiceClient : ITransientDependency
{
Task<ProductDto> GetProductByIdAsync(Guid productId);
}
ProductServiceClient.cs
public class ProductServiceClient : IProductServiceClient
{
private readonly IProductAppService _productAppService;
public ProductServiceClient(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public async Task<ProductDto> GetProductByIdAsync(Guid productId)
{
return await _productAppService.GetAsync(productId);
}
}
OrderAppService.cs
public class OrderAppService : ApplicationService, IOrderAppService
{
private readonly IProductServiceClient _productServiceClient;
private readonly IRepository<Order, Guid> _orderRepository;
public OrderAppService(
IProductServiceClient productServiceClient,
IRepository<Order, Guid> orderRepository)
{
_productServiceClient = productServiceClient;
_orderRepository = orderRepository;
}
public async Task<OrderDto> CreateOrderAsync(CreateOrderDto input)
{
// 远程调用获取产品信息
var product = await _productServiceClient.GetProductByIdAsync(input.ProductId);
// 简单的库存校验
if (product.Stock < input.Quantity)
{
throw new UserFriendlyException("库存不足,无法下单。", "InsufficientStock");
}
// 构造订单实体
var order = new Order(GuidGenerator.Create(), CurrentTenant.Id)
{
ProductId = input.ProductId,
Quantity = input.Quantity,
UnitPrice = product.Price,
TotalPrice = product.Price * input.Quantity,
Status = OrderStatus.Pending
};
// 插入并保存
order = await _orderRepository.InsertAsync(order, autoSave: true);
// 返回映射后的 DTO
return ObjectMapper.Map<Order, OrderDto>(order);
}
}
提示
如果保留手写 ProductServiceClient,记得在模块里 ConfigureServices 注册:
context.Services.AddTransient<IProductServiceClient, ProductServiceClient>();
🚀 HTTP API 客户端代理配置
🌐 1. 在宿主项目的 appsettings.json
中配置远程服务
{
"RemoteServices": {
"ProductService": {
"BaseUrl": "http://product-service" // 💡 使用容器名
}
}
}
🔧 2. 在宿主的 Program.cs
中注册 HTTP 客户端代理
using Volo.Abp.Http.Client.Configuration;
using Product.HttpApi.Client;
var builder = WebApplication.CreateBuilder(args);
// 可选:覆盖或添加 RemoteService 配置 ✨
builder.Services.Configure<RemoteServiceOptions>(options =>
{
// ...
});
// 注册 HttpClient 代理,自动生成 IProductAppService 的实现 🛠️
builder.Services.AddHttpClientProxies(
typeof(ProductHttpApiClientModule).Assembly,
remoteServiceName: "ProductService");
var app = builder.Build();
// 其他中间件...
app.Run();
📦 3. 在 OrderAppService
中注入并调用
public class OrderAppService : ApplicationService, IOrderAppService
{
private readonly IProductAppService _productAppService;
private readonly IRepository<Order, Guid> _orderRepository;
public OrderAppService(
IProductAppService productAppService,
IRepository<Order, Guid> orderRepository)
{
_productAppService = productAppService;
_orderRepository = orderRepository;
}
public async Task<OrderDto> CreateOrderAsync(CreateOrderDto input)
{
// 远程调用获取产品信息
var product = await _productAppService.GetAsync(input.ProductId);
// 简单的库存校验
if (product.Stock < input.Quantity)
{
throw new UserFriendlyException("库存不足,无法下单。","InsufficientStock");
}
// 构造订单实体
var order = new Order(GuidGenerator.Create(), CurrentTenant.Id)
{
ProductId = input.ProductId,
Quantity = input.Quantity,
UnitPrice = product.Price,
TotalPrice = product.Price * input.Quantity,
Status = OrderStatus.Pending
};
// 插入并保存
order = await _orderRepository.InsertAsync(order, autoSave: true);
// 返回映射后的 DTO
return ObjectMapper.Map<Order, OrderDto>(order);
}
}
🌐 API 网关配置
ocelot.json
{
"Routes": [
{
"DownstreamPathTemplate": "/api/product/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "product-service",
"Port": 80
}
],
"UpstreamPathTemplate": "/api/product/{everything}",
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ]
},
{
"DownstreamPathTemplate": "/api/order/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "order-service",
"Port": 80
}
],
"UpstreamPathTemplate": "/api/order/{everything}",
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000"
}
}
Program.cs
var builder = WebApplication.CreateBuilder(args)
builder.Services.AddOcelot(builder.Configuration);
var app = builder.Build();
// 添加路由中间件
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
// 留空即可,由 Ocelot 接管路由
});
// 配置 Ocelot
await app.UseOcelot();
await app.RunAsync();
🔄 分布式事务处理(CAP 集成)
在微服务架构中,分布式事务是一项挑战。CAP 是一个基于 .NET 的分布式事务框架,支持最终一致性和事件驱动。
★ 实现过程可参考我之前的博客:
ABP vNext 集成 CAP + RabbitMQ 实现可靠事件总线
🔢 日志监控
集成 Serilog 作为日志记录框架,配合 Prometheus + Grafana 实现持续性监控和分析:
- 通过
appsettings.json
配置 Serilog 输出到文件或 Elasticsearch - Prometheus + .NET Exporter 接入应用跟踪时间性数据
- Grafana 中查看调用指标、服务状态、运行时间
🚠️ Docker 部署
使用 docker-compose.yml
同时启动多个服务,包括:
- Product / Order 服务
- CAP Dashboard
- RabbitMQ
- API Gateway
示例 docker-compose.yml 配置:
version: '3.4'
networks:
app-net:
driver: bridge
services:
rabbitmq:
image: rabbitmq:3.13-management
container_name: rabbitmq
restart: always
networks:
- app-net
ports:
- "5672:5672" # AMQP 协议端口
- "15672:15672" # 管理界面端口
product-service:
build: ./src/ProductService
container_name: product-service
restart: always
networks:
- app-net
ports:
- "5001:80" # 将容器内部 80 端口映射到宿主机 5001
depends_on:
- rabbitmq
order-service:
build: ./src/OrderService
container_name: order-service
restart: always
networks:
- app-net
ports:
- "5002:80" # 将容器内部 80 端口映射到宿主机 5002
depends_on:
- rabbitmq
- product-service
api-gateway:
build: ./src/ApiGateway
container_name: api-gateway
restart: always
networks:
- app-net
ports:
- "5000:80" # 将容器内部 80 端口映射到宿主机 5000
depends_on:
- product-service
- order-service
cap-dashboard:
image: dnccommunity/cap-dashboard:v6.2.1
container_name: cap-dashboard
restart: always
networks:
- app-net
ports:
- "8080:80" # CAP Dashboard Web UI
depends_on:
- rabbitmq
# 如果需要持久化 RabbitMQ 数据,可在此处添加 volumes 映射
# volumes:
# rabbitmq-data:
# driver: local
📊 总结
本文采用 ABP vNext 框架,实现了从单体到微服务的优雅转换,合理分布模块,支持分布式事务、日志和监控。