博主最近失业在家 , 找工作之余,看了一些关于洋葱(整洁)架构的资料和项目,有感而发,自己动手写了个洋葱架构解决方案,起名叫OnionArch 。基于最新的.Net 7.0 RC1, 数据库采用PostgreSQL, 目前实现了包括多租户在内的12个特性 。
该架构解决方案主要参考了NorthwindTraders,sample-dotnet-core-cqrs-api 项目 , B站上杨中科的课程代码以及博主的一些项目经验 。
洋葱架构的示意图如下:

文章插图
一、OnionArch 解决方案说明解决方案截图如下:

文章插图
可以看到 , 该解决方案轻量化实现了洋葱架构,每个层都只用一个项目表示 。建议将该解决方案作为单个微服务使用,不建议在领域层包含太多的领域根 。
源代码分为四个项目:
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,包括分页查询,方便和其它实体连接后再筛选查询字段 。

文章插图

文章插图
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);}
推荐阅读
- 我的 Kafka 旅程 - Consumer
- 最早人工呼吸救人的医学家是谁 最早采用人工呼吸的医学家是谁
- 宝马gtsuv是什么意思
- 电磁炉插电没开有辐射吗?
- 转炉煤气回收一般采用什么方法
- 合理安排工作时间,提高工作效率 采用工作时间记录法提高工作效率
- 贴梗海棠怎样繁殖,一般采用分株繁殖、压条繁殖、插扦繁殖
- 羽衣甘蓝栽培要点
- 名爵是国产还是合资车品牌 名爵是国产还是合资车?
- 品胜易充如何输出2a