百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

ABP vNext框架文档解读2-模块化Part II

ccwgpt 2024-09-20 13:35 30 浏览 0 评论

在阅读完模块架构的最佳实践&约定后, 我们继续.

实体最佳实践 & 约定

实体

  • 推荐领域层 中定义实体.

主构造函数

  • 推荐 定义一个 主构造函数 确保实体在创建时的有效性, 在代码中通过主构造函数创建实体的新实例.
  • 推荐 根据需求把主构造函数定义为 public,internalprotected internal . 如果它不是public的, 那么应该由领域服务来创建实体.
  • 推荐 总是在主构造函数中初始化子集合.
  • 不推荐 在主构造函数中生成 Guid 键, 应该将其作为参数获取, 在调用时推荐使用 IGuidGenerator 生成新的 Guid 值作为参数.

无参构造函数

  • 推荐 总是定义 protected 无参构造函数与ORM兼容.

类的其它成员

  • 推荐 总是将属性与方法定义为 virtual (除了私有方法 ). 因为有些ORM和动态代理工具需要.
  • 推荐 保持实体在自身边界内始终 有效一致.
    • 推荐 使用 private,protected,internalprotected internal setter定义属性, 保护实体的一致性和有效性.
    • 推荐 定义 public, internalprotected 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、...

取消回复欢迎 发表评论: