# daprplus **Repository Path**: shiningrise/daprplus ## Basic Information - **Project Name**: daprplus - **Description**: DaprPlus微服务框架 - **Primary Language**: C# - **License**: MIT - **Default Branch**: dev/develop - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 10 - **Created**: 2025-03-21 - **Last Updated**: 2025-04-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DaprPlus --- ## 设计概述 DaprPlus是一个基于Dapr的微服务框架,它的目标是提供一个简单、易用、高效的微服务框架,让开发者可以更专注于业务逻辑的开发,而不是花费大量的时间在框架的学习和研究上。 DaprPlus有以下特性: - 基于简洁洋葱架构(Domain/Application/Infrastructure/Api)的经典分层 - Domain层定义所有业务的领域模型,领域事件(服务内),集成事件(服务间),领域服务,领域规则等,不依赖于第三方具体实现 - Infrastructure实现Domain层的定义,使用现有第三方组件提供具体的实现,如数据库访问,消息队列,缓存等 - Api层提供对外的接口,可以是Http,也可以是gRPC,也可以是其他的接口 - 数据存储使用Repository模式基于MediatR的命令和查询责任分离(CQRS)、数据库读写分离的架构实现业务需求 - 多个Repository操作使用UnitOfWork统一管理事务,并在事务提交时在微服务内发布领域事件,对微服务间发布集成事件 - Domain层的Service调用IRepository接口实现数据存储,调用IUnitOfWork接口实现服务内事务控制,不依赖于具体数据存储技术 - Dapr作为Infrastructure层的实现方案之一存在,也可以替换为其他的实现方案 ## 项目结构 ### 模块化设计 DaprPlus设计了一个树形的模块`Pack`系统,各个模块可从启动模块`StartupPack`开始自上而下的模块依赖形成一个模块树。有了这个模块树,框架初始化流程如下: - 从启动模块`StartupPack`开始,加载各个模块及其依赖的模块,获取所有涉及的程序集信息,从这些程序集中可获取到所有类型信息,形成应用程序上下文`ApplicationContext` - ## 模块设计 ### 实体设计 ### 仓储设计 #### 规约模式 - 使用规约模式实现仓储的查询,避免在业务层中编写大量的基于`Expression>`的查询语句,提高代码的可读性和可维护性。 - 即使是临时使用查询表达式来查询,也要使用**有语义的变量命名**来使用`ExpressionSpecification`来进行包装,这样可以在后续的重构中,将查询表达式提取为规约,提高代码的可读性和可维护性。 - 规约模式还可以集成角色/用户数据权限,仅能提取权限内的业务数据 #### 数据上下文IDbContext - IDbContext作为ORM的数据上下文对象在DaprPlus中的引用存在 - 定义了`ToSlave`、`ToMaster`两个API,用于切换数据上下文读写分离指向的主从数据库,便于实现CQRS架构 - 定义数据上下文基类`DbContextBase`作为`IDbContext`接口的实现和实际数据上下文类的基类,实现数据上下文初始化和主从切换 #### 仓储Repository - 仓储接口分只读仓储和可写仓储,只读仓储只提供查询的方法,可写仓储提供增删改的方法,便于实现基于CQRS的读写分离架构。 - 只读仓储接口`IQueryRepository`基于规约模式定义了实体的通用查询功能,独立使用`IQueryRepository`时,将调用`IDbContext.ToSlave`切换到从数据库 - 可写仓储接口`IRepository`继承只读仓储接口`IQueryRepository`,基于规约模式定义了实体的通用增删改功能,将调用`IDbContext.ToMaster`切换到主数据库 - 应避免在仓储接口中返回`IQueryable`类型,以将数据查询控制在仓储范围内,而不是扩展到业务层,避免业务层的数据查询逻辑和仓储层的数据查询逻辑混合在一起,导致代码的可读性和可维护性降低。 - 在实际业务实现中,可根据实际情况,定义各个实体的专用仓储接口,如`IUserRepository`,`IOrderRepository`等,专用仓储接口继承`IQueryRepository`接口,定义实体的专用查询方法,如`GetUserByName`,`GetUserByPhone`等,专用仓储接口的实现类继承`QueryRepository`类,实现专用查询方法的具体实现。 - 批量更新:EF7的批量更新设计的参数类型`SetPropertyCalls`来自于EF7,在Domain的IRepository设计时无法依赖EF7而设计批量更新API,故DaprPlus的批量更新功能使用`Z.EntityFramework.Plus.EFCore`实现 - 批量删除:EF7原生支持批量删除,并且无批量更新的问题,可以用 - Repository中的增删改功能,已经调用了`IDbContext.SaveChanges`,方便在单次调用时频繁执行保存操作。如果一次业务操作涉及多次调用Repository的功能,即存在同时成功或同时失败的需求,需要使用`IUnitOfWork`来开启和提交事务。 #### 工作单元UnitOfWork - 在涉及到多个仓储的操作时,需要使用工作单元来保证事务的一致性,工作单元接口`IUnitOfWork`定义了`BeginOrUseTransactionAsync`、`CommitAsync`、`RollbackAsync`三个方法,分别用于开启事务、提交事务和回滚事务。 - 为解决事务嵌套的问题,在UnitOfWork中设计了一个事务计数,每次调用`BeginOrUseTransactionAsync`时,事务计数+1,每次调用`CommitAsync`时,事务计数-1,当事务计数为1时,才真正提交事务。 ### 领域服务设计 ### 领域事件设计(服务内) ### 集成事件设计(服务间) ### 领域规则设计 ### 应用服务设计