找回密码
 立即注册
首页 业界区 业界 基于NetCorePal Cloud Framework的DDD架构管理系统实践 ...

基于NetCorePal Cloud Framework的DDD架构管理系统实践

榕闹 2026-1-18 23:30:00
基于NetCorePal Cloud Framework的DDD架构管理系统实践

前段时间在做一个管理系统的项目,想尝试一下DDD架构在实际项目中的应用。经过一番调研,最终选择了NetCorePal Cloud Framework作为基础框架,结合.NET 10和Vue 3搭建了一套完整的前后端分离架构。今天就想和大家分享一下这个项目的架构设计和技术选型,希望能给正在做类似项目的朋友一些参考。
项目源码地址:https://github.com/zhouda1fu/Ncp.Admin
项目概述

这个项目是一个典型的企业级管理系统,包含了用户、角色、部门等基础功能模块。在技术选型上,采用了目前比较主流的技术栈:
后端方面,使用.NET 10作为主要框架,配合EF Core做数据访问,FastEndpoints替代传统的Controller,MediatR实现CQRS模式。数据存储支持MySQL、PostgreSQL和SQL Server,消息队列选择了RabbitMQ(通过CAP框架集成),缓存用Redis,还集成了.NET Aspire来做云原生的基础设施管理。
前端部分基于Vben Admin,这是一个非常优秀的Vue 3 + TypeScript + Vite的管理后台模板,UI组件用的是Ant Design Vue,整体体验不错。
架构设计

分层架构

整个项目采用了经典的三层架构,这个结构应该很多做DDD的朋友都比较熟悉。三层之间的依赖关系是单向的:Web层依赖Infrastructure层,Infrastructure层依赖Domain层,Domain层作为核心,不依赖任何其他层。
  1. Ncp.Admin
  2. ├── Domain(领域层)
  3. │   ├── AggregatesModel(聚合模型)
  4. │   └── DomainEvents(领域事件)
  5. ├── Infrastructure(基础设施层)
  6. │   ├── EntityConfigurations(实体配置)
  7. │   └── Repositories(仓储实现)
  8. └── Web(表现层)
  9.     ├── Application(应用服务层)
  10.     │   ├── Commands(命令)
  11.     │   ├── Queries(查询)
  12.     │   └── DomainEventHandlers(领域事件处理器)
  13.     └── Endpoints(API端点)
复制代码
这种分层的好处是职责清晰,Domain层只关注业务逻辑,Infrastructure层负责技术实现,Web层处理HTTP请求和响应。
核心设计模式

1. 领域驱动设计(DDD)

在这个项目中,DDD主要体现在聚合根的设计上。每个聚合根都有自己的业务边界,状态只能通过业务方法来修改。就拿部门这个聚合根来说吧:
  1. /// <summary>
  2. /// 部门ID(强类型ID)
  3. /// </summary>
  4. public partial record DeptId : IInt64StronglyTypedId;
  5. /// <summary>
  6. /// 部门聚合根
  7. /// </summary>
  8. public class Dept : Entity<DeptId>, IAggregateRoot
  9. {
  10.     public string Name { get; private set; } = string.Empty;
  11.     public string Remark { get; private set; } = string.Empty;
  12.     public DeptId ParentId { get; private set; } = default!;
  13.     public int Status { get; private set; } = 1;
  14.    
  15.    
  16.     protected Dept() { }
  17.    
  18.     // 业务方法:更新部门信息
  19.     public void UpdateInfo(string name, string remark, DeptId parentId, int status)
  20.     {
  21.         Name = name;
  22.         Remark = remark;
  23.         ParentId = parentId;
  24.         Status = status;
  25.         UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
  26.         
  27.         // 发布领域事件
  28.         AddDomainEvent(new DeptInfoChangedDomainEvent(this));
  29.     }
  30.    
  31.     // 软删除
  32.     public void SoftDelete()
  33.     {
  34.         if (IsDeleted)
  35.         {
  36.             throw new KnownException("部门已经被删除");
  37.         }
  38.         IsDeleted = true;
  39.         UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
  40.     }
  41. }
复制代码
这里有几个设计点我觉得值得说一下。首先是强类型ID,比如DeptId,这样可以避免把部门ID和用户ID搞混,编译器就能帮你检查出来。其次是属性都用private set,外面不能直接修改,必须通过业务方法,这样就保证了业务规则的一致性。另外,当部门信息变更时会发布领域事件,这样可以通知其他需要同步更新的地方,比如用户表中的部门名称。
2. CQRS模式(命令查询职责分离)

CQRS在这个项目中主要体现在读写分离上。写操作通过命令(Command)来处理,读操作通过查询(Query)来处理。这样做的好处是职责清晰,而且可以针对不同的场景做优化。
写操作这边,命令的定义很简单,就是一个record。每个命令都有对应的验证器和处理器。看一个创建部门的例子:
  1. /// <summary>
  2. /// 创建部门命令
  3. /// </summary>
  4. public record CreateDeptCommand(string Name, string Remark, DeptId? ParentId, int Status)
  5.     : ICommand<DeptId>;
  6. /// <summary>
  7. /// 命令验证器
  8. /// </summary>
  9. public class CreateDeptCommandValidator : AbstractValidator<CreateDeptCommand>
  10. {
  11.     public CreateDeptCommandValidator(DeptQuery deptQuery)
  12.     {
  13.         RuleFor(d => d.Name).NotEmpty().WithMessage("部门名称不能为空");
  14.         RuleFor(d => d.Name)
  15.             .MustAsync(async (n, ct) => !await deptQuery.DoesDeptExist(n, ct))
  16.             .WithMessage(d => $"该部门已存在,Name={d.Name}");
  17.         RuleFor(d => d.Status).InclusiveBetween(0, 1).WithMessage("状态值必须为0或1");
  18.     }
  19. }
  20. /// <summary>
  21. /// 命令处理器
  22. /// </summary>
  23. public class CreateDeptCommandHandler(IDeptRepository deptRepository)
  24.     : ICommandHandler<CreateDeptCommand, DeptId>
  25. {
  26.     public async Task<DeptId> Handle(CreateDeptCommand request, CancellationToken cancellationToken)
  27.     {
  28.         var parentId = request.ParentId ?? new DeptId(0);
  29.         var dept = new Dept(request.Name, request.Remark, parentId, request.Status);
  30.         
  31.         await deptRepository.AddAsync(dept, cancellationToken);
  32.         
  33.         // 注意:不需要手动调用SaveChanges,框架会自动处理
  34.         return dept.Id;
  35.     }
  36. }
复制代码
验证器这里用了FluentValidation,支持同步和异步验证。比如检查部门名称是否已存在这种需要查数据库的验证,就可以用异步的MustAsync。
读操作这边,直接使用DbContext,而且可以用投影来优化性能。比如获取部门树的时候,只选择需要的字段:
  1. /// <summary>
  2. /// 部门查询服务
  3. /// </summary>
  4. public class DeptQuery(ApplicationDbContext applicationDbContext) : IQuery
  5. {
  6.     private DbSet<Dept> DeptSet { get; } = applicationDbContext.Depts;
  7.    
  8.     /// <summary>
  9.     /// 获取部门树(使用投影优化性能)
  10.     /// </summary>
  11.     public async Task<IEnumerable<DeptTreeDto>> GetDeptTreeAsync(
  12.         bool includeInactive = false,
  13.         CancellationToken cancellationToken = default)
  14.     {
  15.         // 使用投影只选择需要的字段,减少内存占用
  16.         var allDepts = await DeptSet.AsNoTracking()
  17.             .WhereIf(!includeInactive, d => d.Status != 0)
  18.             .Select(d => new DeptTreeNode
  19.             {
  20.                 Id = d.Id,
  21.                 Name = d.Name,
  22.                 Remark = d.Remark,
  23.                 ParentId = d.ParentId,
  24.                 Status = d.Status,
  25.                 CreatedAt = d.CreatedAt
  26.             })
  27.             .ToListAsync(cancellationToken);
  28.         
  29.         // 在内存中构建树形结构
  30.         return BuildTreeStructure(allDepts);
  31.     }
  32. }
复制代码
这样读写分离的好处是,查询这边可以针对不同的查询场景做优化,比如用投影减少内存占用,或者将来可以加缓存、用读库等,而不会影响写操作的逻辑。
3. 事件驱动架构

事件驱动这块,项目实现了领域事件和集成事件两种机制。领域事件主要用于聚合内部的同步操作,集成事件用于跨服务通信。
比如说,当部门信息变更的时候,需要同步更新用户表中的部门名称。这个过程就可以通过领域事件来实现:
  1. /// <summary>
  2. /// 部门信息变更领域事件
  3. /// </summary>
  4. public record DeptInfoChangedDomainEvent(Dept Dept) : IDomainEvent;
复制代码
然后在事件处理器中处理这个逻辑:
  1. /// <summary>
  2. /// 部门信息变更领域事件处理器 - 用于更新用户部门名称
  3. /// </summary>
  4. public class DeptInfoChangedDomainEventHandlerForUpdateUserDeptName(
  5.     IMediator mediator,
  6.     UserQuery userQuery)
  7.     : IDomainEventHandler<DeptInfoChangedDomainEvent>
  8. {
  9.     public async Task Handle(DeptInfoChangedDomainEvent domainEvent, CancellationToken cancellationToken)
  10.     {
  11.         var dept = domainEvent.Dept;
  12.         var deptId = dept.Id;
  13.         var newDeptName = dept.Name;
  14.         
  15.         // 查询所有属于该部门的用户ID
  16.         var userIds = await userQuery.GetUserIdsByDeptIdAsync(deptId, cancellationToken);
  17.         
  18.         // 通过Command更新每个用户的部门名称(而不是直接操作数据库)
  19.         foreach (var userId in userIds)
  20.         {
  21.             var command = new UpdateUserDeptNameCommand(userId, newDeptName);
  22.             await mediator.Send(command, cancellationToken);
  23.         }
  24.     }
  25. }
复制代码
这样设计的好处是,部门聚合和用户聚合之间没有直接依赖,通过事件来通信。如果将来需要增加新的业务逻辑,比如部门变更时要发送通知,只需要再加一个事件处理器就行了,不需要改现有的代码。
4. FastEndpoints轻量级API框架

在API设计这块,项目选择了FastEndpoints而不是传统的Controller。主要是觉得FastEndpoints的代码更简洁,性能也更好。一个端点就是一个类,职责清晰。
看一个创建部门的例子:
  1. /// <summary>
  2. /// 创建部门的API端点
  3. /// </summary>
  4. [Tags("Depts")]
  5. public class CreateDeptEndpoint(IMediator mediator)
  6.     : Endpoint<CreateDeptRequest, ResponseData<CreateDeptResponse>>
  7. {
  8.     public override void Configure()
  9.     {
  10.         Post("/api/admin/dept");
  11.         AuthSchemes(JwtBearerDefaults.AuthenticationScheme);
  12.         Permissions(PermissionCodes.AllApiAccess, PermissionCodes.DeptCreate);
  13.     }
  14.    
  15.     public override async Task HandleAsync(CreateDeptRequest req, CancellationToken ct)
  16.     {
  17.         var cmd = new CreateDeptCommand(req.Name, req.Remark, req.ParentId, req.Status);
  18.         var deptId = await mediator.Send(cmd, ct);
  19.         var response = new CreateDeptResponse(deptId, req.Name, req.Remark);
  20.         await Send.OkAsync(response.AsResponseData(), cancellation: ct);
  21.     }
  22. }
复制代码
代码很简洁,一个类就把路由、认证、权限都配置好了。请求和响应都是强类型的,类型安全有保障。而且测试起来也很方便,不需要启动HTTP服务器,直接测端点就行了。
几个核心特性

1. 强类型ID

这个项目里所有聚合根都用强类型ID,而不是直接用long或int。比如部门ID是DeptId,用户ID是UserId。这样做的好处是编译器能帮你检查类型错误,不会把部门ID和用户ID搞混。
使用起来也很简单:
  1. // 定义强类型ID
  2. public partial record DeptId : IInt64StronglyTypedId;
  3. // 使用强类型ID
  4. var deptId = new DeptId(123);
  5. var parentId = request.ParentId ?? new DeptId(0);
复制代码
框架会自动处理序列化和类型转换,用起来很顺手。
2. 仓储模式

仓储这块,写操作通过仓储来处理,查询操作直接使用DbContext。仓储的实现很简单:
  1. /// <summary>
  2. /// 部门仓储接口
  3. /// </summary>
  4. public interface IDeptRepository : IRepository<Dept, DeptId> { }
  5. /// <summary>
  6. /// 部门仓储实现
  7. /// </summary>
  8. public class DeptRepository(ApplicationDbContext context)
  9.     : RepositoryBase<Dept, DeptId, ApplicationDbContext>(context),
  10.       IDeptRepository { }
复制代码
框架会自动管理事务和SaveChanges,命令处理器里不需要手动调用,这样代码更简洁,也不容易出错。
3. 验证机制

验证用的是FluentValidation,支持同步和异步验证。比如创建部门的时候,需要检查部门名称是否已存在,就可以用异步验证:
  1. public class CreateDeptCommandValidator : AbstractValidator<CreateDeptCommand>
  2. {
  3.     public CreateDeptCommandValidator(DeptQuery deptQuery)
  4.     {
  5.         RuleFor(d => d.Name).NotEmpty().WithMessage("部门名称不能为空");
  6.         // 异步验证:检查部门名称是否已存在
  7.         RuleFor(d => d.Name)
  8.             .MustAsync(async (n, ct) => !await deptQuery.DoesDeptExist(n, ct))
  9.             .WithMessage(d => $"该部门已存在,Name={d.Name}");
  10.     }
  11. }
复制代码
4. 异常处理

业务异常用KnownException来处理,框架会自动转换成合适的HTTP状态码。比如在聚合根里:
  1. // 在聚合根中
  2. public void SoftDelete()
  3. {
  4.     if (IsDeleted)
  5.     {
  6.         throw new KnownException("部门已经被删除");
  7.     }
  8.     // ...
  9. }
  10. // 在命令处理器中
  11. var dept = await deptRepository.GetAsync(request.DeptId, cancellationToken)
  12.     ?? throw new KnownException($"未找到部门,DeptId = {request.DeptId}");
复制代码
这样前端收到的错误信息就很清晰,不需要再做额外的转换。
测试策略

测试这块,项目用的是xUnit,集成测试用了Aspire来自动管理测试环境。这样做的好处是不用手动搭建测试数据库、Redis这些基础设施,Aspire会自动启动和管理。
看一个部门创建接口的测试例子:
  1. [Collection(WebAppTestCollection.Name)]
  2. public class DeptTests(WebAppFixture app) : AuthenticatedTestBase<WebAppFixture>(app)
  3. {
  4.     [Fact]
  5.     public async Task CreateDept_WithValidData_ShouldSucceed()
  6.     {
  7.         // Arrange
  8.         var client = await GetAuthenticatedClientAsync();
  9.         var deptName = $"测试部门_{Guid.NewGuid():N}";
  10.         
  11.         try
  12.         {
  13.             // Act
  14.             var request = new CreateDeptRequest(deptName, "测试备注", null, 1);
  15.             var (response, result) = await client.POSTAsync<
  16.                 CreateDeptEndpoint,
  17.                 CreateDeptRequest,
  18.                 ResponseData<CreateDeptResponse>>(request);
  19.             
  20.             // Assert
  21.             Assert.True(response.IsSuccessStatusCode);
  22.             Assert.NotNull(result?.Data);
  23.             Assert.Equal(deptName, result.Data.Name);
  24.         }
  25.         finally
  26.         {
  27.             await CleanupTestDataAsync();
  28.         }
  29.     }
  30. }
复制代码
这种测试方式很接近真实的场景,测试的是完整的HTTP请求流程,而且会自动清理测试数据,保证测试之间的独立性。另外还支持身份认证测试,可以模拟登录用户的各种操作。
前端架构

前端用的是Vben Admin这个模板,这是一个基于Vue 3的管理后台框架。技术栈也比较主流:Vue 3 Composition API、TypeScript、Vite、Ant Design Vue,状态管理用Pinia,路由用Vue Router。
Vben Admin这个框架做得很完善,开箱即用的功能很多。比如权限控制,支持路由权限和按钮权限,用起来很方便。还有国际化支持,可以多语言切换。主题和布局也可以定制,基本的管理后台需求都能满足。
最重要的是类型安全,前后端都用了TypeScript,接口定义好之后,类型检查能帮你发现很多问题。
开发规范

为了让代码质量更统一,项目里制定了一些开发规范。比如文件的组织方式:

  • 聚合根放在 Domain/AggregatesModel/{AggregateName}Aggregate/
  • 领域事件放在 Domain/DomainEvents/
  • 仓储放在 Infrastructure/Repositories/
  • 命令放在 Web/Application/Commands/{Module}Commands/
  • 查询放在 Web/Application/Queries/
  • 端点放在 Web/Endpoints/{Module}Endpoints/
还有一些强制性的要求,比如所有聚合根都用强类型ID,而且不手动赋值ID,依赖EF的值生成器。所有命令都要有对应的验证器。领域事件要在聚合发生改变时发布。命令处理器不能调用SaveChanges,框架会自动处理。仓储必须用异步方法。业务异常用KnownException处理。
另外,项目还提供了很多代码片段,可以快速生成常用代码。比如ncpcmd可以生成命令及其验证器和处理器,ncpar可以生成聚合根,ncprepo可以生成仓储接口和实现,epp可以生成FastEndpoint的完整实现。这样开发效率会高不少。
云原生支持

项目集成了.NET Aspire,这个功能真的很方便。启动开发环境只需要运行AppHost项目,Aspire会自动管理所有依赖服务,不需要手动启动数据库、Redis、RabbitMQ这些。
  1. # 仅需确保Docker环境运行
  2. docker version
  3. # 直接运行AppHost项目,Aspire会自动管理所有依赖服务
  4. cd src/Ncp.Admin.AppHost
  5. dotnet run
复制代码
Aspire会自动启动和管理数据库容器(MySQL、PostgreSQL等)、消息队列容器(RabbitMQ等)、Redis容器,还会提供统一的Aspire Dashboard界面,可以查看所有服务的状态。服务之间的连接字符串也会自动配置,省了很多麻烦。
代码分析可视化

框架还提供了代码流分析和可视化功能,这个对理解架构很有帮助。可以通过命令行工具生成HTML文件:
  1. # 安装全局工具
  2. dotnet tool install -g NetCorePal.Extensions.CodeAnalysis.Tools
  3. # 生成可视化文件
  4. cd src/Ncp.Admin.Web
  5. netcorepal-codeanalysis generate --output architecture.html
复制代码
支持生成架构流程图、命令链路图、事件流程图、类图等,可以直观地看到代码之间的关系和数据流向。
总结

这个项目算是一个DDD架构的实践案例,展示了如何在.NET 10生态中应用DDD、CQRS、事件驱动这些架构思想。整体架构清晰,职责分明,代码组织得也比较规范。
技术栈上,后端用.NET 10 + EF Core + FastEndpoints + MediatR,前端用Vue 3 + TypeScript + Vite,都是目前比较主流的技术。开发体验上,有代码片段、自动化工具,还有完善的开发规范,开发效率还可以。
可维护性这块,代码分层清晰,测试支持也比较完善,还有代码可视化工具,方便新人理解架构。云原生支持也很到位,Aspire让基础设施管理变得简单。
如果你也在做类似的管理系统,或者想了解DDD在实际项目中的应用,可以看看这个项目的代码,应该能有一些参考价值。项目地址在https://github.com/zhouda1fu/Ncp.Admin,欢迎交流讨论。
参考资料

最后附上一些相关的参考资料,有兴趣的朋友可以深入了解一下:

  • NetCorePal Cloud Framework - 项目使用的基础框架
  • FastEndpoints - 轻量级API框架
  • Vben Admin - 前端管理后台模板
  • .NET Aspire - 云原生应用开发平台
项目源码地址:https://github.com/zhouda1fu/Ncp.Admin

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2026-1-22 03:40:17

举报

2026-1-23 07:19:18

举报

2026-1-25 10:27:07

举报

2026-1-27 05:04:58

举报

2026-1-27 08:04:21

举报

2026-2-8 20:40:07

举报

2026-2-16 10:11:57

举报

2026-2-26 10:07:35

举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册