OnionArch - 采用DDD+CQRS+.Net 7.0實現的洋蔥架構

博主最近失業在家 , 找工作之余,看了一些關于洋蔥(整潔)架構的資料和項目,有感而發,自己動手寫了個洋蔥架構解決方案,起名叫OnionArch ?;谧钚碌?Net 7.0 RC1, 數據庫采用PostgreSQL, 目前實現了包括多租戶在內的12個特性 。
該架構解決方案主要參考了NorthwindTraders,sample-dotnet-core-cqrs-api 項目 ,  B站上楊中科的課程代碼以及博主的一些項目經驗 。
洋蔥架構的示意圖如下:

OnionArch - 采用DDD+CQRS+.Net 7.0實現的洋蔥架構

文章插圖
一、OnionArch 解決方案說明解決方案截圖如下:
OnionArch - 采用DDD+CQRS+.Net 7.0實現的洋蔥架構

文章插圖
可以看到 , 該解決方案輕量化實現了洋蔥架構,每個層都只用一個項目表示 。建議將該解決方案作為單個微服務使用,不建議在領域層包含太多的領域根 。
源代碼分為四個項目:
1. OnionArch.Domain- 核心領域層,類庫項目 , 其主要職責實現每個領域內的業務邏輯 。設計每個領域的實體(Entity),值對象、領域事件和領域服務,在領域服務中封裝業務邏輯,為應用層服務 。-   領域層也包含數據庫倉儲接口,緩存接口、工作單元接口、基礎實體、基礎領域跟實體、數據分頁實體的定義 , 以及自定義異常等 。
2. OnionArch.Infrastructure- 基礎架構層,類庫項目,其主要職責是實現領域層定義的各種接口適配器(Adapter) 。例如數據庫倉儲接口、工作單元接口和緩存接口,以及領域層需要的其它系統集成接口 。-  基礎架構層也包含Entity Framework基礎DbConext、ORM配置的定義和數據遷移記錄 。
3. OnionArch.Application- 應用(業務用例)層,類庫項目,其主要職責是通過調用領域層服務實現業務用例 。一個業務用例通過調用一個或多個領域層服務實現 。不建議在本層實現業務邏輯 。-   應用(業務用例)層也包含業務用例實體(Model)、Model和Entity的映射關系定義,業務實基礎命令接口和查詢接口的定義(CQRS),包含公共MediatR管道(AOP)處理和公共Handler的處理邏輯 。
4. OnionArch.GrpcService- 界面(API)層,GRPC接口項目,用于實現GRPC接口 。通過MediatR特定業務用例實體(Model)消息來調用應用層的業務用例 。- 界面(API)層也包含對領域層接口的實現,例如通過HttpContext獲取當前租戶和賬號登錄信息 。
二、OnionArch已實現特性說明1.支持多租戶(通過租戶字段)基于Entity Framework實體過濾器和實現對租戶數據的查詢過濾
protected override void OnModelCreating(ModelBuilder modelBuilder){//加載配置modelBuilder.ApplyConfigurationsFromAssembly(typeof(TDbContext).Assembly);//為每個繼承BaseEntity實體增加租戶過濾器// Set BaseEntity rules to all loaded entity typesforeach (var entityType in GetBaseEntityTypes(modelBuilder)){var method = SetGlobalQueryMethod.MakeGenericMethod(entityType);method.Invoke(this, new object[] { modelBuilder, entityType });}}在BaseDbContext文件的SaveChanges之前對實體租戶字段賦值
//為每個繼承BaseEntity的實體的Id主鍵和TenantId賦值var baseEntities = ChangeTracker.Entries<BaseEntity>();foreach (var entry in baseEntities){switch (entry.State){case EntityState.Added:if (entry.Entity.Id == Guid.Empty)entry.Entity.Id = Guid.NewGuid();if (entry.Entity.TenantId == Guid.Empty)entry.Entity.TenantId = _currentTenantService.TenantId;break;}}多租戶支持全部在底層實現,包括租戶字段的索引配置等 。開發人員不用關心多租戶部分的處理邏輯,只關注業務領域邏輯也業務用例邏輯即可 。
2.通用倉儲和緩存接口實現了泛型通用倉儲接口,批量更新和刪除方法基于最新的Entity Framework 7.0 RC1,為提高查詢效率 , 查詢方法全部返回IQueryable,包括分頁查詢,方便和其它實體連接后再篩選查詢字段 。
OnionArch - 采用DDD+CQRS+.Net 7.0實現的洋蔥架構

文章插圖
OnionArch - 采用DDD+CQRS+.Net 7.0實現的洋蔥架構

文章插圖
public interface IBaseRepository<TEntity> where TEntity : BaseEntity{Task<TEntity> Add(TEntity entity);Task AddRange(params TEntity[] entities);Task<TEntity> Update(TEntity entity);Task<int> UpdateRange(Expression<Func<TEntity, bool>> whereLambda, Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls);Task<int> UpdateByPK(Guid Id, Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls);Task<TEntity> Delete(TEntity entity);Task<int> DeleteRange(Expression<Func<TEntity, bool>> whereLambda);Task<int> DeleteByPK(Guid Id);Task<TEntity> DeleteByPK2(Guid Id);Task<TEntity> SelectByPK(Guid Id);IQueryable<TEntity> SelectRange<TOrder>(Expression<Func<TEntity, bool>> whereLambda, Expression<Func<TEntity, TOrder>> orderbyLambda, bool isAsc = true);Task<PagedResult<TEntity>> SelectPaged<TOrder>(Expression<Func<TEntity, bool>> whereLambda, PagedOption pageOption, Expression<Func<TEntity, TOrder>> orderbyLambda, bool isAsc = true);Task<bool> IsExist(Expression<Func<TEntity, bool>> whereLambda);}

推薦閱讀