从单体到微服务:基于 ABP vNext 模块化设计的演进之路

#王者杯·14天创作挑战营·第1期#

🚀 从单体到微服务:基于 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

💡 远程调用方案

以下示例展示两种远程调用方案,任选其一即可,不必同时使用。

特性手写防腐层(ProductServiceClientABP 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 框架,实现了从单体到微服务的优雅转换,合理分布模块,支持分布式事务、日志和监控。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值