ABP vNext框架文档解读2-模块化Part II
ccwgpt 2024-09-20 13:35 30 浏览 0 评论
在阅读完模块架构的最佳实践&约定后, 我们继续.
实体最佳实践 & 约定
实体
- 推荐 在 领域层 中定义实体.
主构造函数
- 推荐 定义一个 主构造函数 确保实体在创建时的有效性, 在代码中通过主构造函数创建实体的新实例.
- 推荐 根据需求把主构造函数定义为 public,internal 或 protected internal . 如果它不是public的, 那么应该由领域服务来创建实体.
- 推荐 总是在主构造函数中初始化子集合.
- 不推荐 在主构造函数中生成 Guid 键, 应该将其作为参数获取, 在调用时推荐使用 IGuidGenerator 生成新的 Guid 值作为参数.
无参构造函数
- 推荐 总是定义 protected 无参构造函数与ORM兼容.
类的其它成员
- 推荐 总是将属性与方法定义为 virtual (除了私有方法 ). 因为有些ORM和动态代理工具需要.
- 推荐 保持实体在自身边界内始终 有效 和 一致.
- 推荐 使用 private,protected,internal或protected internal setter定义属性, 保护实体的一致性和有效性.
- 推荐 定义 public, internal 或 protected internal (virtual)方法在必要时更改属性值(使用非public setters时).
聚合根
主键
- 推荐 总是使用 Id 属性作为聚合根主键.
- 不推荐 在聚合根中使用 复合主键.
- 推荐 所有的聚合根都使用 Guid 类型 主键.
基类
- 推荐 聚合尽可能小. 大多数聚合只有原始属性, 不会有子集合. 把这些视为设计决策:
- 加载和保存聚合的 性能 与 内存 成本 (请记住,聚合通常是作为一个单独的单元被加载和保存的). 较大的聚合会消耗更多的CPU和内存.
- 一致性 & 有效性 边界.
仓储最佳实践 & 约定
仓储接口
- 推荐 在领域层中定义仓储接口.
- 推荐 根据每个聚合根定义仓储接口(如 IIdentityUserRepository)并创建相应的实现.
- 推荐 在应用代码中使用仓储时应该注入仓储接口.
- 不推荐 在应用代码中使用泛型仓储接口(如 IRepository<IdentityUser, Guid>).
- 不推荐 在应用代码(领域, 应用... 层)中使用 IQueryable<TEntity> 特性.
- 不推荐 仓储接口继承 IRepository<TEntity, TKey> 接口. 因为它继承了 IQueryable 而仓储不应该将IQueryable暴露给应用.
- 推荐 通常仓储接口继承自 IBasicRepository<TEntity, TKey> 或更低级别的接口, 如在需要的时候继承 IReadOnlyRepository<TEntity, TKey> .
- 不推荐 为实体定义仓储接口,因为它们不是聚合根.
仓储方法
- 推荐 所有的仓储方法定义为 异步.
- 推荐 为仓储的每个方法添加 可选参数 cancellationToken
Task<IdentityUser> FindByNormalizedUserNameAsync(
[NotNull] string normalizedUserName,
CancellationToken cancellationToken = default
);
- 推荐 为仓储的每个异步方法创建一个 同步扩展 方法
public static class IdentityUserRepositoryExtensions
{
public static IdentityUser FindByNormalizedUserName(
this IIdentityUserRepository repository,
[NotNull] string normalizedUserName)
{
return AsyncHelper.RunSync(
() => repository.FindByNormalizedUserNameAsync(normalizedUserName)
);
}
}
- 推荐 为仓储中返回单个实体的方法添加一个可选参数 bool includeDetails = true (默认值为true)
Task<IdentityUser> FindByNormalizedUserNameAsync(
[NotNull] string normalizedUserName,
bool includeDetails = true,
CancellationToken cancellationToken = default
);
- 推荐 为仓储中返回实体列表的方法添加一个可选参数 bool includeDetails = false (默认值为false)
Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
string normalizedRoleName,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
- 不推荐 创建复合类通过调用仓储单个方法返回组合实体. 比如: UserWithRoles, UserWithTokens, UserWithRolesAndTokens. 相反, 正确的使用 includeDetails 选项, 在需要时加载实体所有的详细信息.
- 避免 为了从仓储中获取实体的部分属性而为实体创建投影类. 比如: 避免通过创建BasicUserView来选择所需的一些属性. 相反可以直接使用聚合根类. 不过这条规则有例外情况:
- 性能对于用例来说非常重要,而且使用整个聚合根对性能的影响非常大.
应用服务最佳实践 & 约定
- 推荐 为每个 聚合根 创建一个应用服务
应用服务接口
- 推荐 在 application.contracts层中为每一个应用服务定义一个接口.
- 推荐 继承 IApplicationService 接口 .
- 推荐 接口名称使用AppService 后缀 (如: IProductAppService).
- 推荐 为服务创建输入输出DTO(数据传输对象).
- 不推荐 服务中含有返回实体的方法.
- 推荐 根据DTO 最佳实践定义DTO.
输出
- 避免 为相同或相关实体定义过多的输出DTO. 为实体定义 基础 和 详细 DTO.
基础DTO
- 推荐 为聚合根定义一个基础DTO.
- 直接包含实体中所有的原始属性.例外: 出于安全原因,可以排除某些属性(像 User.Password).
- 包含实体中所有子集合, 每个集合项都是一个简单的关系DTO.
[Serializable]
public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Text { get; set; }
public Guid? MilestoneId { get; set; }
public Collection<IssueLabelDto> Labels { get; set; }
}
[Serializable]
public class IssueLabelDto
{
public Guid IssueId { get; set; }
public Guid LabelId { get; set; }
}
详细DTO
- 推荐 如果实体持有对其他聚合根的引用,那么应该为其定义详细DTO.
- 直接包含实体中所有的 原始属性.
- 例外-1: 出于安全原因,可以排除某些属性(像 User.Password).
- 例外-2: 推荐 排除引用属性(如上例中的 MilestoneId). 为其添加引用属性的详细信息.
- 为每个引用属性添加其基本DTO .
- 包含实体的所有子集合, 集合中的每项都是相关实体的基本DTO.
[Serializable]
public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Text { get; set; }
public MilestoneDto Milestone { get; set; }
public Collection<LabelDto> Labels { get; set; }
}
[Serializable]
public class MilestoneDto : ExtensibleEntityDto<Guid>
{
public string Name { get; set; }
public bool IsClosed { get; set; }
}
[Serializable]
public class LabelDto : ExtensibleEntityDto<Guid>
{
public string Name { get; set; }
public string Color { get; set; }
}
输入
- 不推荐 在输入DTO中定义未在服务类中使用的属性.
- 不推荐 在应用服务方法之间共享输入DTO.
- 不推荐 继承另一个输入DTO类.
- 可以 继承自抽象基础DTO类, 并以这种方式在不同的DTO之间共享一些属性. 但是在这种情况下需要非常小心, 因为更新基础DTO会影响所有相关的DTO和服务方法. 所以避免这样做是一种好习惯.
方法
- 推荐 为异步方法使用 Async 后缀.
- 不推荐 在方法名中重复实体的名称.
- 例如: 在 IProductAppService 中定义GetAsync(...) 而不是 GetProductAsync(...) .
获取单一实体
- 推荐 使用 GetAsync 作为方法名.
- 推荐 使用id作为方法参数.
- 返回 详细DTO.
Task<QuestionWithDetailsDto> GetAsync(Guid id);
获取实体集合
- 推荐 使用 GetListAsync 作为方法名.
- 推荐 如果需要获取单个DTO可以使用参数进行 过滤, 排序 和 分页.
- 推荐 尽可能让过滤参数可选.
- 推荐 将排序与分页属性设置为可选, 并且提供默认值.
- 推荐 限制最大页数大小 (基于性能考虑).
- 推荐 返回 详细DTO集合.
Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
创建一个新实体
- 推荐 使用 CreateAsync 作为方法名.
- 推荐 使用专门的输入DTO来创建实体.
- 推荐 DTO类从 ExtensibleObject 类继承(或任何实现 ExtensibleObject的类) 以允许在需要时传递额外的属性.
- 推荐 使用 data annotations 进行输入验证.
- 尽可能在领域之间共享常量(通过domain shared package定义的常量).
- 推荐 只需要创建实体的最少信息, 但是提供了其他可选属性.
Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);
[Serializable]
public class CreateQuestionDto : ExtensibleObject
{
[Required]
[StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)]
public string Title { get; set; }
[StringLength(QuestionConsts.MaxTextLength)]
public string Text { get; set; } //Optional
public Guid? CategoryId { get; set; } //Optional
}
更新已存在的实体
- 推荐 使用 UpdateAsync 作为方法名.
- 推荐 使用专门的输入DTO来更新实体.
- 推荐 DTO类从 ExtensibleObject 类继承(或任何实现 ExtensibleObject的类) 以允许在需要时传递额外的属性.
- 推荐 获取实体的id作为分离的原始参数. 不要包含更新DTO.
- 推荐 使用 data annotations 进行输入验证.
- 尽可能在领域之间共享常量(通过domain shared package定义的常量).
- 推荐 返回更新实体的详细DTO.
Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto);
删除已存在的实体
- 推荐 使用 DeleteAsync 作为方法名.
- 推荐 使用原始参数 id.
Task DeleteAsync(Guid id);
其他方法
- 可以 定义其他方法以对实体执行操作.
Task<int> VoteAsync(Guid id, VoteType type);
应用服务实现
- 推荐 开发完全独立于web层的应用层.
- 推荐 在应用层实现应用服务接口.
- 推荐 使用命名约定. 如: 为 IProductAppService 接口创建 ProductAppService 类.
- 推荐 继承自 ApplicationService 基类.
- 推荐 将所有的公开方法定义为 virtual, 以便开发人员继承和覆盖它们.
- 不推荐 定义 private 方法. 应该定义为 protected virtual, 这样开发人员可以继承和覆盖它们.
使用仓储
- 推荐 使用专门设计的仓储 (如 IProductRepository).
- 不推荐 使用泛型仓储 (如 IRepository<Product>).
查询数据
- 不推荐 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询.
额外的属性
- 推荐 使用 MapExtraPropertiesTo 扩展方法 (参阅) 或配置对象映射 (MapExtraProperties) 以允许应用开发人员能够扩展对象和服务.
操作/删除 实体
- 推荐 总是从数据库中获取所有的相关实体以对他们执行操作.
- 推荐 更新实体后调用存储的Update/UpdateAsync方法.因为并非所有数据库API都支持更改跟踪和自动更新.
使用其他应用服务
- 不推荐 使用相同 模块/应用程序 的其他应用服务. 相反;
- 使用领域层执行所需的任务.
- 提取新类并在应用程序服务之间共享, 在必要时代码重用. 但要小心不要结合两个用例. 它们在开始时可能看起来相似, 但可能会随时间演变为不同的方向. 请谨慎使用代码共享.
- 可以 在以下情况下使用其他应用服务;
- 它们是另一个模块/微服务的一部分.
- 当前模块仅引用已使用模块的application contracts.
数据传输对象最佳实践 & 约定
- 推荐 在 application.contracts 层中定义DTO.
- 推荐 在可能和必要的情况下从预构建的 基础DTO类 继承 (如 EntityDto<TKey>, CreationAuditedEntityDto<TKey>, AuditedEntityDto<TKey>, FullAuditedEntityDto<TKey> 等).
- 推荐 从聚合根的扩展DTO继承(如 ExtensibleAuditedEntityDto<TKey>), 因为聚合根是可扩展的额外的属性使用这种方式映射到DTO.
- 推荐 定义 public getter 和 setter 的DTO成员 .
- 推荐 使用 data annotations 验证 service输入DTO的属性.
- 不推荐 在DTO中添加任何 逻辑, 在必要的时候可以实现 IValidatableObject 接口.
- 推荐 为所有的DTO标记 [Serializable] Attribute. 因为它们已经是可序列化的, 开发人员可能会希望进行二进制序列化.
相关推荐
- Spring WebFlux vs. Spring MVC(springboot是什么)
-
背景随着异步I/O和Netty等框架的流行,响应式编程逐渐走入大众的视野。但是,响应式编程本身并不是太新的概念,这个术语最早出现在1985年DavidHarel和AmirPnue...
- 深度解析微服务高并发:适配SpringMVC框架适配模块及实现原理
-
适配主流框架如果不借助Sentinel提供的适配主流框架的模块,则在使用Sentinel时需要借助try-catchfinally将要保护的资源(方法或代码块)包起来,在目标方法或代码块执行之前,调...
- Spring MVC 底层原理深度解析:从请求到响应的全链路拆解
-
一、Servlet容器与DispatcherServlet的启动博弈1.Tomcat初始化阶段java//Tomcat初始化流程StandardContext#startInterna...
- 改造总结之传统SpringMVC架构转换为SpringBoot再到集群
-
改造出发点,是基于现在服务都在向上云的目标前进,传统SpringMVC难以满足项目持续构建、服务节点任意扩展的需求,所以开始了历史项目的改造。项目改造考虑的主要是兼容以前的业务代码,以及session...
- SpringBoot3 整合 Spring MVC 全解析:开启高效 Web 开发之旅
-
在当今的JavaWeb开发领域,Spring框架家族无疑占据着重要的地位。其中,SpringBoot3和SpringMVC更是开发者们构建强大、高效Web应用的得力工具。今天,...
- 一文读懂SpringMVC(一文读懂!残疾人低保边缘家庭能领的超实用福利政策)
-
1.SpringMVC定义1.1.MVC定义Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据View(视图):是应用程序中处理数据显示的部分。通常...
- 69 个Spring mvc 全部注解:真实业务使用案例说明(必须收藏)
-
SpringMVC框架的注解为Web开发提供了一种简洁而强大的声明式方法。从控制器的定义、请求映射、参数绑定到异常处理和响应构建,这些注解涵盖了Web应用程序开发的各个方面。它们不仅简化了编码工作,...
- Spring MVC工作原理:像拼积木一样构建Web应用
-
SpringMVC工作原理:像拼积木一样构建Web应用在Java的Web开发领域,SpringMVC无疑是一个让人又爱又恨的存在。它像一位神通广大的积木搭建大师,将一个个分散的功能模块巧妙地拼接在...
- 5千字的SpringMVC总结,我觉得你会需要
-
思维导图文章已收录到我的Github精选,欢迎Star:https://github.com/yehongzhi/learningSummary概述SpringMVC再熟悉不过的框架了,因为现在最火的...
- SpringMVC工作原理与优化指南(springmvc工作原理和工作流程)
-
SpringMVC工作原理与优化指南在现代Java开发中,SpringMVC无疑是构建Web应用程序的首选框架之一。它以其优雅的设计和强大的功能吸引了无数开发者。那么,SpringMVC究竟是如何工作...
- Spring MVC框架源码深度剖析:从入门到精通
-
SpringMVC框架源码深度剖析:从入门到精通SpringMVC框架简介SpringMVC作为Spring框架的一部分,为构建Web应用程序提供了强大且灵活的支持。它遵循MVC(Model-V...
- 3000字搞明白SpringMVC工作流程、DispatcherServlet类、拦截器!
-
SpringMVC基础虽然SpringBoot近几年发展迅猛,但是SpringMVC在Web开发领域仍然占有重要的地位。本章主要讲解SpringMVC的核心:DispatcherServlet类...
- 多年经验大佬用2000字透彻解析SpringMVC的常用注解及相关示例
-
SpringMVC注解SpringMVC框架提供了大量的注解,如请求注解、参数注解、响应注解及跨域注解等。这些注解提供了解决HTTP请求的方案。本节主要讲解SpringMVC的常用注解及相关示例...
- 知乎热议:如何成为前端架构师,赚百万年薪?
-
作者|慕课网精英讲师双越最近有一条知乎热议:从一个前端工程师,如何根据目标,制定计划,才能快速进阶成为前端架构师?不久之前我参与了一次直播,讲到了自己对于Web前端架构师的理解。架构师这个角色...
- 学习笔记-前端开发架构设计(前端架构设计方案)
-
前端开发的技术选项主要包含以下几点,下面对一些名词概念的解释做了笔记:1、分层架构:把功能相似,抽象级别相近的实现进行分层隔离优势:松散耦合(易维护,易复用,易扩展)常见分层方式:MVC,MVVM2、...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Spring WebFlux vs. Spring MVC(springboot是什么)
- 深度解析微服务高并发:适配SpringMVC框架适配模块及实现原理
- Spring MVC 底层原理深度解析:从请求到响应的全链路拆解
- 改造总结之传统SpringMVC架构转换为SpringBoot再到集群
- SpringBoot3 整合 Spring MVC 全解析:开启高效 Web 开发之旅
- 一文读懂SpringMVC(一文读懂!残疾人低保边缘家庭能领的超实用福利政策)
- 69 个Spring mvc 全部注解:真实业务使用案例说明(必须收藏)
- Spring MVC工作原理:像拼积木一样构建Web应用
- 5千字的SpringMVC总结,我觉得你会需要
- SpringMVC工作原理与优化指南(springmvc工作原理和工作流程)
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- bootstrap框架 (43)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- express框架 (43)
- springmvc框架 (49)
- scrapy框架 (52)
- beego框架 (42)
- java框架spring (43)
- grpc框架 (55)
- orm框架有哪些 (43)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)