1.0 ABP Business版 10.0实战指南:框架入门与企业级应用构建
一、什么是ABP框架?深度解析企业级开发利器
ABP(ASP.NET Boilerplate)是一个完整的企业级应用开发框架,基于ASP.NET Core平台构建,提供了一套经过验证的开发模型和最佳实践。其核心目标是帮助开发者显著提高开发效率、保证代码质量并简化复杂业务逻辑的实现。
1.1 ABP框架的起源与发展:从MVC到云原生
ABP框架由土耳其开发者Halil İbrahim Kalkan于2013年开始开发,最初基于ASP.NET MVC 5。经过十余年的发展,ABP已成为.NET生态系统中最受欢迎的企业级框架之一,拥有超过13k GitHub星标和10k+社区成员。
| 年份 | 版本 | 里程碑 |
|---|---|---|
| 2013 | v1.0 | ABP框架首次发布,基于ASP.NET MVC 5 |
| 2017 | v2.0 | 全面支持ASP.NET Core,拥抱跨平台 |
| 2020 | v5.0 | 引入模块化设计和微服务架构支持 |
| 2022 | v7.0 | 推出Business商业版,集成更多企业功能 |
| 2024 | v10.0 | 增强AI集成和云原生支持,适应现代开发需求 |
1.2 ABP框架的核心特性:企业级开发的完整解决方案
ABP框架提供了全方位的企业级功能,以下是其核心特性的深入解析:
| 特性 | 技术实现 | 商业价值 |
|---|---|---|
| 模块化设计 | 基于AbpModule类的模块化系统 |
实现代码复用,支持团队并行开发,降低维护成本 |
| DDD分层架构 | Domain/Application/Infrastructure/Presentation四层架构 | 清晰的关注点分离,提高代码可维护性和扩展性 |
| 增强型依赖注入 | 基于.NET Core DI,提供高级特性如属性注入、装饰器模式 | 简化对象管理,便于单元测试和组件替换 |
| 约定优于配置 | 自动注册服务、路由映射、权限检查等 | 减少样板代码,提高开发效率 |
| 多租户支持 | 内置租户隔离机制,支持共享/独立数据库 | 快速构建SaaS平台,降低运营成本 |
| 身份验证与授权 | 集成OpenIddict,支持OAuth2.0/OpenID Connect | 企业级安全保障,支持单点登录和第三方登录 |
| 多语言本地化 | 基于资源文件和动态切换机制 | 便于构建全球化应用,支持动态添加语言 |
| 分布式缓存 | 内置Redis支持,提供缓存标签和自动失效机制 | 显著提高应用性能,支持高并发场景 |
| 事件总线 | 支持本地事件和RabbitMQ/Azure Service Bus分布式事件 | 实现松耦合架构,便于系统扩展和集成 |
| 审计日志 | 自动记录所有操作,支持自定义审计实体 | 满足合规要求,便于问题追踪和安全审计 |
| 后台作业 | 支持立即执行、延迟执行和周期性任务 | 处理耗时操作,提高用户体验 |
| 自动API文档 | 集成Swagger UI和ReDoc,支持OAuth2.0授权 | 自动生成交互式API文档,提高API可用性 |
二、ABP框架的分层架构:DDD思想的最佳实践
ABP框架严格遵循**领域驱动设计(DDD)**原则,采用分层架构设计。这种架构设计的核心优势在于:
- 分离关注点:每个层只负责特定职责,降低耦合度
- 提高可测试性:便于单元测试和集成测试
- 支持团队协作:不同团队可以专注于不同层的开发
- 便于技术演进:可以独立升级或替换某一层的技术栈
2.1 核心分层深度解析
Domain层:业务核心,领域模型的家园
Domain层是应用程序的核心,包含业务领域的核心逻辑和规则。它是整个系统中最稳定的部分,不应依赖任何外部框架或技术。
核心组件:
- 实体(Entity):具有唯一标识符的领域对象,如
Book、Order - 值对象(Value Object):无唯一标识的不可变对象,如
Address、Money - 聚合根(Aggregate Root):领域的根实体,维护聚合内的一致性,如
Order是OrderItem的聚合根 - 仓储接口(Repository Interface):定义数据访问契约,如
IBookRepository - 领域服务(Domain Service):实现跨实体的业务逻辑,如
OrderProcessingService - 领域事件(Domain Event):领域内发生的重要事件,如
OrderCreatedEvent - 领域规则(Business Rule):封装业务约束,如
OrderTotalMustBePositiveRule
代码示例:领域模型设计
// 订单状态枚举
public enum OrderStatus
{
[Description("待处理")] Pending,
[Description("已确认")] Confirmed,
[Description("已发货")] Shipped,
[Description("已完成")] Completed,
[Description("已取消")] Cancelled
}
// 订单项值对象
public class OrderItem : ValueObject
{
public Guid ProductId { get; private set; }
public string ProductName { get; private set; }
public decimal UnitPrice { get; private set; }
public int Quantity { get; private set; }
// 计算属性
public decimal TotalPrice => UnitPrice * Quantity;
// 私有构造函数,防止直接创建
private OrderItem() { }
// 工厂方法,保证值对象的完整性
public static OrderItem Create(Guid productId, string productName, decimal unitPrice, int quantity)
{
Check.NotNullOrEmpty(productName, nameof(productName));
Check.Positive(unitPrice, nameof(unitPrice));
Check.Positive(quantity, nameof(quantity));
return new OrderItem
{
ProductId = productId,
ProductName = productName,
UnitPrice = unitPrice,
Quantity = quantity
};
}
// ValueObject实现
protected override IEnumerable<object> GetAtomicValues()
{
yield return ProductId;
yield return ProductName;
yield return UnitPrice;
yield return Quantity;
}
}
// 订单聚合根
public class Order : AuditedAggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; private set; } // 多租户支持
// 实体属性
public string OrderNumber { get; private set; }
public OrderStatus Status { get; private set; }
public decimal TotalAmount { get; private set; }
// 子实体集合
private readonly List<OrderItem> _items = new();
public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
// 私有构造函数,防止直接创建
private Order() { }
// 工厂方法,保证领域一致性
public static Order Create(Guid? tenantId, string orderNumber, List<OrderItem> items)
{
// 验证业务规则
Check.NotNullOrWhiteSpace(orderNumber, nameof(orderNumber));
Check.NotNullOrEmpty(items, nameof(items));
// 计算订单总金额
var totalAmount = items.Sum(item => item.TotalPrice);
if (totalAmount <= 0)
{
throw new BusinessException("订单总金额必须大于0");
}
// 创建订单
var order = new Order
{
Id = Guid.NewGuid(),
TenantId = tenantId,
OrderNumber = orderNumber,
Status = OrderStatus.Pending,
TotalAmount = totalAmount
};
// 添加订单项
foreach (var item in items)
{
order._items.Add(item);
}
// 发布领域事件
order.AddDomainEvent(new OrderCreatedEvent(order.Id, orderNumber, totalAmount));
return order;
}
// 领域方法:确认订单
public void Confirm()
{
if (Status != OrderStatus.Pending)
{
throw new BusinessException("只有待处理状态的订单才能确认");
}
Status = OrderStatus.Confirmed;
AddDomainEvent(new OrderConfirmedEvent(Id, OrderNumber));
}
// 领域方法:发货
public void Ship()
{
if (Status != OrderStatus.Confirmed)
{
throw new BusinessException("只有已确认状态的订单才能发货");
}
Status = OrderStatus.Shipped;
AddDomainEvent(new OrderShippedEvent(Id, OrderNumber));
}
// 领域方法:完成订单
public void Complete()
{
if (Status != OrderStatus.Shipped)
{
throw new BusinessException("只有已发货状态的订单才能完成");
}
Status = OrderStatus.Completed;
AddDomainEvent(new OrderCompletedEvent(Id, OrderNumber));
}
// 领域方法:取消订单
public void Cancel()
{
if (Status == OrderStatus.Completed || Status == OrderStatus.Cancelled)
{
throw new BusinessException("已完成或已取消的订单无法再次取消");
}
Status = OrderStatus.Cancelled;
AddDomainEvent(new OrderCancelledEvent(Id, OrderNumber));
}
}
// 订单创建领域事件
public class OrderCreatedEvent : DomainEvent
{
public Guid OrderId { get; }
public string OrderNumber { get; }
public decimal TotalAmount { get; }
public OrderCreatedEvent(Guid orderId, string orderNumber, decimal totalAmount)
{
OrderId = orderId;
OrderNumber = orderNumber;
TotalAmount = totalAmount;
}
}
Application层:业务用例,协调领域与表示层
Application层负责实现业务用例,协调Domain层和Presentation层之间的交互。它是连接领域模型和外部世界的桥梁。
核心组件:
- 应用服务(Application Service):暴露API给表示层,如
OrderAppService - 数据传输对象(DTO):用于层间数据传输,防止领域模型泄露,如
CreateOrderDto - 权限定义(Permission):声明应用程序的权限,如
OrderManagement - 验证逻辑:基于数据注解和自定义验证器
- 工作单元(Unit of Work):管理数据库事务,确保数据一致性
代码示例:应用服务实现
// 订单创建DTO
public class CreateOrderDto
{
[Required]
public List<CreateOrderItemDto> Items { get; set; } = new();
[Required]
public Guid CustomerId { get; set; }
public string? Notes { get; set; }
}
// 订单项创建DTO
public class CreateOrderItemDto
{
[Required]
public Guid ProductId { get; set; }
[Required]
[Range(1, int.MaxValue)]
public int Quantity { get; set; }
}
// 订单详情DTO
public class OrderDto : AuditedEntityDto<Guid>
{
public string OrderNumber { get; set; }
public OrderStatus Status { get; set; }
public string StatusName => Status.GetDescription();
public decimal TotalAmount { get; set; }
public List<OrderItemDto> Items { get; set; } = new();
public string? Notes { get; set; }
public Guid CustomerId { get; set; }
public string CustomerName { get; set; }
}
// 订单项DTO
public class OrderItemDto
{
public Guid ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TotalPrice => UnitPrice * Quantity;
}
// 订单应用服务接口
public interface IOrderAppService : ICrudAppService<
OrderDto, // 显示DTO
Guid, // 主键类型
OrderListRequestDto, // 列表请求DTO
CreateOrderDto, // 创建DTO
UpdateOrderDto // 更新DTO
>
{
// 自定义方法:确认订单
Task ConfirmAsync(Guid id);
// 自定义方法:发货
Task ShipAsync(Guid id);
// 自定义方法:完成订单
Task CompleteAsync(Guid id);
// 自定义方法:取消订单
Task CancelAsync(Guid id);
// 自定义方法:获取订单详情
Task<OrderDto> GetDetailedAsync(Guid id);
}
// 订单列表请求DTO
public class OrderListRequestDto : PagedAndSortedResultRequestDto
{
public OrderStatus? Status { get; set; }
public Guid? CustomerId { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string? Keyword { get; set; }
}
// 订单应用服务实现
[Authorize(OrderPermissions.Default)]
public class OrderAppService : CrudAppService<
Order, // 实体类型
OrderDto, // DTO类型
Guid, // 主键类型
OrderListRequestDto, // 列表请求DTO
CreateOrderDto, // 创建DTO
UpdateOrderDto // 更新DTO
>, IOrderAppService
{
private readonly IRepository<Order, Guid> _orderRepository;
private readonly IRepository<Product, Guid> _productRepository;
private readonly IRepository<Customer, Guid> _customerRepository;
private readonly OrderManager _orderManager;
// 构造函数注入依赖
public OrderAppService(
IRepository<Order, Guid> repository,
IRepository<Product, Guid> productRepository,
IRepository<Customer, Guid> customerRepository,
OrderManager orderManager)
: base(repository)
{
_orderRepository = repository;
_productRepository = productRepository;
_customerRepository = customerRepository;
_orderManager = orderManager;
}
// 重写创建方法,使用领域服务
[Authorize(OrderPermissions.Create)]
public override async Task<OrderDto> CreateAsync(CreateOrderDto input)
{
// 1. 验证客户是否存在
var customer = await _customerRepository.FindAsync(input.CustomerId);
if (customer == null)
{
throw new UserFriendlyException("客户不存在");
}
// 2. 构建订单项
var orderItems = new List<OrderItem>();
foreach (var itemDto in input.Items)
{
var product = await _productRepository.GetAsync(itemDto.ProductId);
// 检查库存
if (product.Stock < itemDto.Quantity)
{
throw new UserFriendlyException($"产品 {product.Name} 库存不足,当前库存: {product.Stock}");
}
// 创建订单项
var orderItem = OrderItem.Create(
product.Id,
product.Name,
product.Price,
itemDto.Quantity);
orderItems.Add(orderItem);
// 扣减库存
product.DecreaseStock(itemDto.Quantity);
await _productRepository.UpdateAsync(product);
}
// 3. 使用领域服务创建订单
var order = await _orderManager.CreateAsync(
CurrentTenant.Id, // 多租户支持
input.CustomerId,
orderItems,
input.Notes
);
// 4. 保存订单
await _orderRepository.InsertAsync(order);
// 5. 返回DTO(包含客户信息)
var orderDto = MapToGetOutputDto(order);
orderDto.CustomerName = customer.Name;
return orderDto;
}
// 重写查询方法,支持过滤和关联查询
public override async Task<PagedResultDto<OrderDto>> GetListAsync(OrderListRequestDto input)
{
// 使用ABP的查询构建器,支持自动过滤、排序和分页
var query = await Repository.GetQueryableAsync();
// 关联查询客户信息
query = query.Include(o => o.Customer);
// 应用过滤条件
if (input.Status.HasValue)
{
query = query.Where(o => o.Status == input.Status.Value);
}
if (input.CustomerId.HasValue)
{
query = query.Where(o => o.CustomerId == input.CustomerId.Value);
}
if (input.StartDate.HasValue)
{
query = query.Where(o => o.CreationTime >= input.StartDate.Value);
}
if (input.EndDate.HasValue)
{
query = query.Where(o => o.CreationTime <= input.EndDate.Value.AddDays(1));
}
if (!string.IsNullOrWhiteSpace(input.Keyword))
{
var keyword = input.Keyword.ToLower();
query = query.Where(o =>
o.OrderNumber.ToLower().Contains(keyword) ||
o.Customer.Name.ToLower().Contains(keyword));
}
// 执行分页查询
var totalCount = await AsyncExecuter.CountAsync(query);
var items = await AsyncExecuter.ToListAsync(
query
.OrderBy(input.Sorting ?? nameof(Order.CreationTime) + " desc")
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
);
// 映射到DTO
return new PagedResultDto<OrderDto>
{
TotalCount = totalCount,
Items = ObjectMapper.Map<List<Order>, List<OrderDto>>(items)
};
}
// 自定义方法:确认订单
[Authorize(OrderPermissions.Edit)]
public async Task ConfirmAsync(Guid id)
{
var order = await _orderRepository.GetAsync(id);
order.Confirm();
await _orderRepository.UpdateAsync(order);
}
// 自定义方法:发货
[Authorize(OrderPermissions.Edit)]
public async Task ShipAsync(Guid id)
{
var order = await _orderRepository.GetAsync(id);
order.Ship();
await _orderRepository.UpdateAsync(order);
}
// 自定义方法:完成订单
[Authorize(OrderPermissions.Edit)]
public async Task CompleteAsync(Guid id)
{
var order = await _orderRepository.GetAsync(id);
order.Complete();
await _orderRepository.UpdateAsync(order);
}
// 自定义方法:取消订单
[Authorize(OrderPermissions.Delete)]
public async Task CancelAsync(Guid id)
{
var order = await _orderRepository.GetAsync(id);
order.Cancel();
await _orderRepository.UpdateAsync(order);
// 恢复库存(示例逻辑)
foreach (var item in order.Items)
{
var product = await _productRepository.GetAsync(item.ProductId);
product.IncreaseStock(item.Quantity);
await _productRepository.UpdateAsync(product);
}
}
// 自定义方法:获取订单详情
public async Task<OrderDto> GetDetailedAsync(Guid id)
{
// 获取订单及关联数据
var order = await _orderRepository.GetAsync(
id,
includeDetails: true // 自动加载导航属性
);
// 映射到DTO
return MapToGetOutputDto(order);
}
// 重写映射配置,确保正确映射关联属性
protected override void MapExtraPropertiesToDto(OrderDto dto, Order order)
{
base.MapExtraPropertiesToDto(dto, order);
if (order.Customer != null)
{
dto.CustomerName = order.Customer.Name;
}
}
}
Infrastructure层:技术实现,支持领域层
Infrastructure层负责提供技术实现细节,支持Domain层和Application层的抽象接口。
核心组件:
- 仓储实现:基于EF Core的数据库访问实现
- 数据库上下文:EF Core
DbContext配置 - 缓存实现:Redis缓存的具体实现
- 外部服务集成:如支付网关、短信服务等
Presentation层:用户界面,与用户交互
Presentation层是面向用户的界面,负责处理用户请求和展示数据。
核心组件:
- MVC控制器:处理HTTP请求,返回视图或JSON数据
- Razor Pages:ASP.NET Core的页面模型,适合简单页面
- 视图组件:可重用的UI组件,如导航菜单、数据表格
- API控制器:提供RESTful API,支持前后端分离架构
三、ABP框架的模块化设计:构建可扩展的企业应用
模块化是ABP框架的核心设计原则,它允许将应用程序拆分为独立的功能模块,每个模块可以独立开发、测试、部署和升级。
3.1 模块的优势:企业级开发的必然选择
- 代码复用:模块可以在多个应用程序中复用,降低开发成本
- 团队协作:不同团队可以并行开发不同模块,提高开发效率
- 可维护性:模块边界清晰,便于定位和修复问题
- 按需加载:可以根据需要加载或卸载模块,优化应用启动时间
- 技术演进:可以独立升级模块,降低技术栈迁移风险
3.2 ABP模块的结构:标准化的模块开发方式
每个ABP模块都遵循标准化的结构,确保模块间的兼容性和互操作性:
MyModule/
├── MyModule.Domain/ # 领域层
├── MyModule.Application/ # 应用层
├── MyModule.Application.Contracts/ # 应用服务接口
├── MyModule.EntityFrameworkCore/ # EF Core实现
├── MyModule.Web/ # Web UI实现
└── MyModule.MongoDB/ # MongoDB实现(可选)
3.3 模块开发示例:构建图书管理模块
模块类定义:
[DependsOn(
typeof(AbpDddDomainModule),
typeof(AbpAuditLoggingDomainModule))]
public class BookStoreDomainModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 1. 配置领域服务
context.Services.AddDomainEventHandlers(
typeof(BookStoreDomainModule).Assembly);
// 2. 配置EF Core
Configure<AbpDbContextOptions>(options =>
{
options.UseSqlServer();
});
// 3. 注册自定义仓储
context.Services.AddScoped<IBookRepository, BookRepository>();
// 4. 配置领域事件总线
Configure<AbpEventBusOptions>(options =>
{
options.UseOutboxEventBus();
});
}
}
四、ABP框架与其他框架的对比:为什么选择ABP?
4.1 ABP vs ASP.NET Core MVC:企业级功能的差异
| 评估维度 | ASP.NET Core MVC | ABP框架 |
|---|---|---|
| 基础架构 | ✅ 提供Web框架基础 | ✅ 基于ASP.NET Core,提供完整企业功能 |
| 开发效率 | ⭐⭐⭐ 需手动实现大部分功能 | ⭐⭐⭐⭐⭐ 内置大量企业级功能,减少开发时间 |
| 代码质量 | ⭐⭐⭐ 取决于团队规范 | ⭐⭐⭐⭐⭐ 强制DDD架构,保证代码一致性 |
| 扩展性 | ⭐⭐⭐ 需自行设计扩展机制 | ⭐⭐⭐⭐⭐ 模块化设计,支持热插拔 |
| 安全性 | ⭐⭐⭐ 基础安全特性 | ⭐⭐⭐⭐⭐ 企业级身份验证和授权 |
| 可维护性 | ⭐⭐⭐ 取决于架构设计 | ⭐⭐⭐⭐⭐ 清晰的分层架构和模块化设计 |
| 社区支持 | ⭐⭐⭐⭐⭐ 微软官方支持 | ⭐⭐⭐⭐ 活跃的开源社区和商业支持 |
4.2 ABP vs Spring Boot:跨平台框架的对比
| 评估维度 | Spring Boot | ABP框架 |
|---|---|---|
| 平台支持 | ✅ Java平台 | ✅ .NET平台,支持跨平台 |
| 模块化设计 | ✅ Spring Modules | ✅ 更严格的模块化系统 |
| DDD支持 | ✅ 需第三方库 | ✅ 内置完整DDD支持 |
| 多租户 | ❌ 需自行实现 | ✅ 内置多租户支持 |
| 开发体验 | ⭐⭐⭐⭐ 成熟的Java生态 | ⭐⭐⭐⭐⭐ .NET Core的现代开发体验 |
| 商业支持 | ⭐⭐⭐ 多种商业支持选项 | ⭐⭐⭐⭐ 官方商业版和支持服务 |
五、ABP框架的应用场景:适合哪些企业?
ABP框架适合各种规模的企业应用开发,特别是:
5.1 大型企业应用:复杂业务的可靠解决方案
- 核心优势:模块化设计便于团队协作,DDD架构保证代码质量,企业级安全特性
- 典型应用:ERP系统、CRM系统、供应链管理系统
5.2 SaaS平台:快速构建多租户应用
- 核心优势:内置多租户支持,灵活的数据库策略,便于扩展
- 典型应用:项目管理工具、人力资源管理系统、在线教育平台
5.3 微服务架构:构建可扩展的分布式系统
- 核心优势:模块化设计适合微服务拆分,支持服务发现和负载均衡
- 典型应用:电商平台、金融系统、物联网平台
5.4 企业门户网站:现代化的企业形象展示
- 核心优势:内置CMS模块,支持多语言,响应式设计
- 典型应用:企业官网、产品展示网站、新闻门户
六、ABP框架的社区与生态:强大的支持体系
ABP框架拥有活跃的社区和丰富的生态系统,为开发者提供全方位的支持:
6.1 社区资源:学习和交流的平台
- 官方文档:ABP.io Documentation - 详细的官方文档和教程
- GitHub仓库:abpframework/abp - 开源代码和问题追踪
- Discord社区:ABP Discord - 超过10k开发者的交流平台
- Stack Overflow:ABP标签 - 超过5k问题和解答
- YouTube频道:ABP Framework - 视频教程和技术分享
6.2 生态系统:丰富的工具和模块
- ABP CLI:命令行工具,快速创建和管理ABP项目
- ABP Studio:可视化开发工具,提供代码生成和项目管理功能
- LeptonX主题:现代化的响应式主题,支持多种布局
- 商业模块:如CMS、聊天、AI管理、文件管理等企业级模块
七、ABP框架的最佳实践:企业级开发的黄金法则
- 严格遵循DDD分层架构:保持各层职责清晰,避免层间耦合
- 优先使用领域事件:实现松耦合架构,便于系统扩展
- 合理设计聚合根:控制聚合大小,避免过大的聚合
- 充分利用模块化设计:将功能拆分为独立模块,提高可维护性
- 使用DTO进行层间通信:保护领域模型,提高安全性
- 实现完整的单元测试:确保核心业务逻辑的正确性
- 利用缓存提高性能:合理使用缓存,避免数据库瓶颈
- 实现全面的日志记录:便于问题追踪和性能监控
八、总结:选择ABP框架的商业价值
ABP框架是企业级.NET开发的最佳选择,它提供了完整的开发解决方案,帮助企业:
- 降低开发成本:减少样板代码,提高开发效率
- 提高代码质量:强制DDD架构,保证代码一致性和可维护性
- 加速上市时间:内置大量企业级功能,快速构建应用
- 降低技术风险:成熟的框架,活跃的社区支持
- 支持业务扩展:模块化设计,便于系统扩展和功能添加
对于企业来说,选择ABP框架不仅是技术上的选择,更是商业上的明智决策。它可以帮助企业快速构建高质量的企业应用,适应市场变化,保持竞争优势。
在接下来的文章中,我们将深入探讨ABP Business商业版的特性和应用场景,帮助你理解商业版带来的额外价值和投资回报。
参考资料:
- ABP官方文档 - 最新的官方指南和API参考
- ABP框架GitHub仓库 - 开源代码和贡献指南
- Halil İbrahim Kalkan的博客 - 框架作者的技术分享
- ABP框架设计文档 - 框架设计原理和架构决策
- DDD领域驱动设计实战 - ABP中的DDD实践指南
更多推荐


所有评论(0)