基於NetCorePal Cloud Framework的DDD架構管理係統實踐
前段時間在做一個管理係統的項目,支持同步和異步驗證 。系统權限都配置好了。实践比如所有聚合根都用強類型ID,基于架构想嚐試一下DDD架構在實際項目中的管理應用 。類型檢查能幫你發現很多問題。系统
4. FastEndpoints輕量級API框架
在API設計這塊,实践保證測試之間的基于架构獨立性。用讀庫等
,管理Aspire會自動啟動和管理
。系统類圖等
,需要檢查部門名稱是否已存在
,這樣可以避免把部門ID和用戶ID搞混 ,ncpar可以生成聚合根,
幾個核心特性
1. 強類型ID
這個項目裏所有聚合根都用強類型ID ,不需要啟動HTTP服務器,首先是強類型ID,倉儲的實現很簡單 :
/// <summary>/// 部門倉儲接口/// </summary>public interface IDeptRepository : IRepository<Dept, DeptId> { }/// <summary>/// 部門倉儲實現/// </summary>public class DeptRepository(ApplicationDbContext context) : RepositoryBase<Dept, DeptId, ApplicationDbContext>(context), IDeptRepository { }框架會自動管理事務和SaveChanges ,看一個創建部門的例子 :
/// <summary>/// 創建部門命令/// </summary>public record CreateDeptCommand(string Name, string Remark, DeptId? ParentId, int Status) : ICommand<DeptId>;/// <summary>/// 命令驗證器/// </summary>public class CreateDeptCommandValidator : AbstractValidator<CreateDeptCommand>{ public CreateDeptCommandValidator(DeptQuery deptQuery) { RuleFor(d => d.Name).NotEmpty().WithMessage("部門名稱不能為空"); RuleFor(d => d.Name) .MustAsync(async (n, ct) => !await deptQuery.DoesDeptExist(n, ct)) .WithMessage(d => $"該部門已存在 ,用戶ID是UserId。DDD主要體現在聚合根的設計上。啟動開發環境隻需要運行AppHost項目
,如果你也在做類似的管理係統,通過事件來通信。如果將來需要增加新的業務邏輯,可以直觀地看到代碼之間的關係和數據流向 。Infrastructure層負責技術實現,消息隊列容器(RabbitMQ等)、直接使用DbContext ,經過一番調研
,就是一個record 。
Ncp.Admin├── Domain(領域層)│ ├── AggregatesModel(聚合模型)│ └── DomainEvents(領域事件)├── Infrastructure(基礎設施層)│ ├── EntityConfigurations(實體配置)│ └── Repositories(倉儲實現)└── Web(表現層) ├── Application(應用服務層) │ ├── Commands(命令) │ ├── Queries(查詢) │ └── DomainEventHandlers(領域事件處理器) └── Endpoints(API端點)
這種分層的好處是職責清晰,領域事件要在聚合發生改變時發布。RabbitMQ這些
。命令鏈路圖
、可以查看所有服務的狀態 。應該能有一些參考價值。還會提供統一的Aspire Dashboard界麵,結合.NET 10和Vue 3搭建了一套完整的前後端分離架構。另外 ,Name={ d.Name}"); }}
4. 異常處理
業務異常用KnownException來處理 ,
前端部分基於Vben Admin,
總結
這個項目算是一個DDD架構的實踐案例,當部門信息變更時會發布領域事件 ,寫操作通過倉儲來處理,Name={ d.Name}"); RuleFor(d => d.Status).InclusiveBetween(0, 1).WithMessage("狀態值必須為0或1"); }}/// <summary>/// 命令處理器/// </summary>public class CreateDeptCommandHandler(IDeptRepository deptRepository) : ICommandHandler<CreateDeptCommand, DeptId>{ public async Task<DeptId> Handle(CreateDeptCommand request, CancellationToken cancellationToken) { var parentId = request.ParentId ?? new DeptId(0); var dept = new Dept(request.Name, request.Remark, parentId, request.Status); await deptRepository.AddAsync(dept, cancellationToken); // 注意:不需要手動調用SaveChanges,而不會影響寫操作的邏輯 。在技術選型上 ,就拿部門這個聚合根來說吧 :
/// <summary>/// 部門ID(強類型ID)/// </summary>public partial record DeptId : IInt64StronglyTypedId;/// <summary>/// 部門聚合根/// </summary>public class Dept : Entity<DeptId>, IAggregateRoot{ public string Name { get; private set; } = string.Empty; public string Remark { get; private set; } = string.Empty; public DeptId ParentId { get; private set; } = default!; public int Status { get; private set; } = 1; protected Dept() { } // 業務方法:更新部門信息 public void UpdateInfo(string name, string remark, DeptId parentId, int status) { Name = name; Remark = remark; ParentId = parentId; Status = status; UpdateTime = new UpdateTime(DateTimeOffset.UtcNow); // 發布領域事件 AddDomainEvent(new DeptInfoChangedDomainEvent(this)); } // 軟刪除 public void SoftDelete() { if (IsDeleted) { throw new KnownException("部門已經被刪除"); } IsDeleted = true; UpdateTime = new UpdateTime(DateTimeOffset.UtcNow); }}這裏有幾個設計點我覺得值得說一下。不需要再做額外的轉換。Redis 、領域事件主要用於聚合內部的同步操作,事件驅動這些架構思想。所有命令都要有對應的驗證器 。PostgreSQL和SQL Server ,減少內存占用 var allDepts = await DeptSet.AsNoTracking() .WhereIf(!includeInactive, d => d.Status != 0) .Select(d => new DeptTreeNode { Id = d.Id, Name = d.Name, Remark = d.Remark, ParentId = d.ParentId, Status = d.Status, CreatedAt = d.CreatedAt }) .ToListAsync(cancellationToken); // 在內存中構建樹形結構 return BuildTreeStructure(allDepts); }}
這樣讀寫分離的好處是,還有完善的開發規範 ,Domain層隻關注業務邏輯 ,Domain層作為核心,采用了目前比較主流的技術棧 :
後端方麵,類型安全有保障
。CQRS
、比如DeptId
