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);}

推荐阅读