ABP vNext框架文档解读2-模块化Part II
ccwgpt 2024-09-20 13:35 39 浏览 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. 因为它们已经是可序列化的, 开发人员可能会希望进行二进制序列化.
相关推荐
- 用Deepseek扩写土木工程毕业论文实操指南
-
用Deepseek扩写毕业论文实操指南一、前期准备整理现有论文初稿/提纲列清楚论文核心框架(背景、现状、意义、方法、数据、结论等)梳理好关键文献,明确核心技术路线二、Deepseek扩写核心思路...
- 985学霸亲授,DeepSeek也能绘6大科研图表,5分钟就出图
-
在实验数据处理中,高效可视化是每个科研人的必修课。传统绘图软件操作复杂、耗时费力,而智能工具DeepSeek的出现彻底改变了这一现状。本文将详解如何用DeepSeek一键生成六大科研常用图表,从思维导...
- AI写论文刷屏?大学生正在丢掉的思考力
-
一、宿舍深夜:当论文变成"Ctrl+C+V"凌晨两点的大学宿舍,小王对着电脑屏幕叹气。本该三天前开始写的近代史论文,此刻还一片空白。他熟练打开某AI写作网站,输入"论五四运动的...
- Grok在辅助论文写作上能不能既“聪明”又“可怕”?!
-
AcademicIdeas-学境思源AI初稿写作随着人工智能技术的飞速发展,论文写作这一学术任务正迎来新的助力。2025年2月18日,美国xAI公司推出了备受瞩目的Grok3模型,其创始人埃隆·...
- 大四论文沟通场景!音频转文字难题听脑AI来化解
-
大四学生都知道,写论文时和导师沟通修改意见,简直是“过关斩将”。电话、语音沟通完,想把导师说的修改方向、重点要求记下来,麻烦事儿可不少。手写记不全,用普通录音转文字工具,转完还得自己慢慢找重点,稍不注...
- 论文写作 | 技术路线图怎么画?(提供经典优秀模板参考)
-
技术路线图是一种图表或文字说明,用于描述研究目标、方法和实施计划。它展示了研究的整体框架和步骤,有助于读者理解研究的逻辑和进展。在课题及论文中,技术路线图是常见的一部分,甚至是一个类似心脏一样的中枢器...
- 25年信息系统项目管理师考试第2批论文题目写作建议思路框架
-
25年信息系统项目管理师考试第2批论文题目写作建议思路框架--马军老师
- 微信购物应尽快纳入法律框架(微信购物管辖)
-
符向军近日,甘肃省工商行政管理局发布《2016年上半年信息分析报告》。报告显示,微信网购纠纷迅猛增长,网络购物投诉呈上升趋势。投诉的主要问题有出售的商品质量不过关、消费者通过微信付款后对方不发货、购买...
- 泛珠三角区域网络媒体与腾讯微信签署《战略合作框架协议》
-
新海南客户端、南海网7月14日消息(记者任桐)7月14日上午,参加第四届泛珠三角区域合作网络媒体论坛的区域网络媒体负责人及嘉宾一行到腾讯微信总部座谈交流,并签署《战略合作框架协议》(以下简称《框架协...
- 离线使用、植入微信-看乐心Mambo手环如何打破框架
-
从2014年开始智能手环就成功进入人们的生活,至今已经演变出数据监测、信息推送、心率监测等诸多五花八门的功能,人们选择智能手环并不指望其能够改变身体健康情况,更多的是通过数据来正视自身运动情况和身体健...
- 华专网络:如何零基础制作一个网站出来?
-
#如何零基础制作一个网站出来?#你是不是觉得网站建设很复杂,觉得自己是小白,需求不明确、流程搞不懂、怕被外包公司坑……这些问题我都懂!今天华专网络就用大白话给你捋清楚建站的全流程,让你轻松get网站制...
- WAIC2024丨明日上午9点,不见不散!共同探讨智能社会与全球治理框架
-
大咖云集,硕果闪耀WAIC2024世界人工智能大会智能社会论坛将于7月5日9:00-12:00与你相约直播间WAIC2024上海杨浦同济大学哔哩哔哩多平台同步直播探讨智能社会与全球治理框架WAIC...
- 约基奇:森林狼换来戈贝尔时大家都在嘲笑 他们的阵容框架很不错
-
直播吧5月4日讯西部季后赛半决赛,掘金将迎战森林狼,约基奇赛前接受采访。约基奇说道:“当蒂姆-康纳利(森林狼总经理、前掘金总经理&曾选中约基奇)做了那笔交易(换来戈贝尔)时,每个人都在嘲笑他...
- 视频号带货为什么一个流量都没有?顶级分析框架送给你
-
视频号带货为什么一个流量都没有?遇到问题,一定是步步来分析内容,视频号带货一个流量都没有,用另外一个意思来讲,就可以说是零播放。为什么视频号带货一个流量都没有?跟你说再多,都不如来个分析框架。1、是否...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)