围绕DDDABP Framework两个核心技术,后面还会陆续发布核心构件实现综合案例实现系列文章,敬请关注! ABP Framework 研习社(QQ群:726299208) ABP Framework 学习及实施DDD经验分享;示例源码、电子书共享,欢迎加入!

数据传输对象

DTO 是简单对象,用于在应用层和展示层传递状态数据。所以,应用服务方法返回 DTO。

DTO原则和最佳实践:

•DTO应该可序列化,因为大多数时候,需要网络传输。•应该有一个无参构造函数•不能包含任何业务逻辑•不能继承或引用实体

输入DTO输出DTO在本质上不同:一个用于给应用服务方法传递参数,一个作为应用服务方法的返回值,根据业务需要区别对待。

输入DTO最佳实践

不要在输入DTO中定义不使用的属性

只定义需要用的属性,否则,无用的属性只会让客户端在使用应用服务方法时感到困惑。当然可以定义可选属性,但是确保当客户端在使用时,不应该影响到用例的工作方式。

这条规则看起来没什么必要,谁会为方法仓储(输入DTO)添加不使用的属性呢?但是,它经常发生,尤其是当你想重用输入DTO对象时,会将多个DTO属性放在一个DTO对象中。

不要重用输入DTO

为每个用例(应用服务方法)定义特定的输入DTO,否则,在某些情况下不会添加一些不被使用的属性,这就违反了上面定义的规则。

有时候,在两个不同的用例中使用相同的DTO似乎很有吸引力,因为他们如此相似。甚至,当前是一模一样,可能后面随着业务变化才会有可能不同,此时也应该不要重用输入DTO。因为和用例间的耦合相比,代码复制可能是更好的做法。

重用DTO的另一种方式是:DTO继承,这同样会产生上面描述的问题.。

示例:用户应用服务

public interface IUserAppService:IApplicationService
{Task CreateAsync(UserDto input);Task UpdateAsync(UserDto input);Task ChangePasswordAsync(UserDto input);
}

IUserAppService 在所有方法(用例)使用 UserDto 作为输入DTO,UserDto定义如下:

public class UserDto
{public Guid Id{get;set;}public string UserName{get;set;}public string Email{get;set;}public string Password{get;set;}public DateTime CreationTime{get;set;}
}

Id 在 Create 方法中不被使用,因为 Id 由服务器生成。•Password 在 Update 方法中不使用,因为有修改密码的单独方法。•CreationTime 未被使用,且不应该由客户端发送给服务端,应该在服务端设置创建时间。

正确的实现,如下:

public interface IUserAppService:IApplicationService
{Task CreateAsync(UserCreationDto input);Task UpdateAsync(UserUpdateDto input);Task ChangePasswordAsync(UserChangePasswordDto input);
}

然后定义对应的DTO类:

public class UserCreationDto
{public string UserName {get;set;}public string Email{get;set;}public string Password{get;set;}
}public class UserUpdateDto
{public Guid Id{get;set;}public string UserName{get;set;}public string Email{get;set;}
}public class UserChangePasswordDto
{public Guid Id{get;set;}public string Password{get;set;}
}

尽管需要编写更多的代码,但是这是一种更易维护的方法。

特殊情况:举个例子,如果你有一个报表页,页面中有多个过滤条件,对应多个应用服务方法(显示报表、导出Excel、导出CSV),此时应该使用相同的输入DTO参数,返回不同的结果。因为当页面过滤条件改变时,修改一个DTO而对整个页面对应的应用服务方法参数生效。

输入DTO中验证逻辑

•仅在DTO内部执行简单验证,使用数据注解特性或实现 IValidatableObject 接口•不要执行领域验证,举个例子,不要在DTO中检测用户名是否唯一的验证。

示例:使用数据注解特性

using System.ComponentModel.DataAnnotations;namespace IssueTracking.Users
{public class UserCreationDto{[Required][StringLength(UserConsts.MaxUserNameLength)]public string UserName {get;set;}[Required][EmailAddress][StringLength(UserConsts.MaxEmailLength)]public string Email{get;set;}[Required][StringLength(UserConsts.MaxEmailLength,MinimumLength=UserConsts.MinPasswordLength)]public string Password{get;set;}}
}

ABP框架自动验证输入DTO,验证失败则抛出AbpValidationException异常,返回 400 HTTP 状态码。

某些开发者认为将验证规则和DTO类分离可能会更好。我们认为声明式(数据注解)是实用的,不会导致任何设计问题。当然,ABP支持 FluentValidation集成。

输出DTO最佳实践

•保持输出DTO数量最小,尽可能重用,但是不能将输入DTO作为输出DTO使用。•输出DTO可以包含比用例需要的更多属性•Create 和 Update 方法中返回DTO

以上建议的主要原因是:

•使客户端代码易于开发和扩展

•在客户端端处理不同但相似的DTO容易混淆•输入DTO中的更多属性可能未来会在UI/客户端中被使用,返回实体的所有属性(已经考虑过安全性和特殊情况)使客户端代码易于改进,而不需要修改后端代码。•如果是通过API暴露给第三方客户端,避免不同需求返回不同DTO

•使服务端代码易于开发和扩展

•更少的类,易于理解和维护•可以重用实体到DTO(AutoMapper)的对象映射代码•不同方法返回相同类型,使添加新方法变得简单明了。

示例:从不同方法返回不同DTO

public interface IUserAppService:IApplicationService
{UserDto Get(Guid id);List<UserNameAndEmailDto> GetUserNameAndEmail(Guid id);List<string> GetRoles(Guid id);List<UserListDto> GetList();UserCreateResultDto Create(UserCreationDto input);UserUpdateResultDto Update(UserUpdateDto input);
}

示例中没有使用异步方法,在实际开发时应该是异步方法。

上面的示例代码中,为每个方法返回不同DTO类型,这样会导致我们需要处理非常多的数据查询,映射实体到DTO的重复代码。

按照以下方式定义就简单多了:

public interface IUserAppService:IApplicationService
{UserDto Get(Guid id);List<UserDto> GetList();UserDto Create(UserCreationDto input);UserDto Update(UserUpdateDto input);
}

使用一个输出DTO:

public class UserDto
{public Guid Id{get;set;}public string UserName{get;set;}public string Email{get;set;}public DateTiem CreationTime{get;set;}public List<string> Roles{get;set;}
}

•移除 GetUserNameAndEmail 和 GetRoles 方法,因为 Get 方法已经返回足够需要的信息。•GetList 返回对象与 Get 相同•Create 和 Update 同样返回 UserDto

由此可见,返回相同DTO更加简洁。

为什么创建或更新之后要返回DTO? 想象一个用例场景,在页面中显示表格数据,当更新之后,获取返回对象,并对表格数据源进行更新,这样就不需要再次调用 GetList 方法,这是我们建议在 Create 和 Update 方法中返回 DTO 的原因。

讨论

以上关于输出DTO的建议,并不适用所有场景。

出于性能考虑,这些建议可以被忽略,特别是当存在大型数据集返回结果时,或者用户界面需要发起很多并发请求时,此时应该创建特定的输出DTO,只包含尽可能少的信息。

可维护性和性能,需要开发者权衡,上面的建议适用于性能损失可忽略不计的应用。

对象映射

自动对象映射是一个非常有用的工具,两个对象的属性相同或相似,将一个对象的值复制给另一个对象。

DTO和实体类通常具有相同或相似的属性,通常需要根据实体和业务需求来创建DTO对象。ABP框架对象映射基于 AutoMapper,相比手动赋值,效率更高。

•仅对实体到输出DTO使用自动对象映射。•输入DTO到实体不适用自动对象映射。

不使用输入DTO到实体自动映射的原因:

1.实体类通常有构造函数,接收参数并在创建时,进行参数验证。自动对象映射操作通常需要无参构造函数创建对象。2.实体属性设置器大多是私有的,应该使用方法设置属性值。3.通常需要仔细验证和处理用户/客户端输入,而不是盲目地映射到实体属性。

虽然其中一些问题可以通过映射配置来解决(例如,AutoMapper允许定义自定义映射规则),但它使你的业务逻辑隐含/隐藏,并与基础设施紧密耦合。我们认为业务代码应该是明确的、清晰的、容易理解的。

学习帮助

围绕DDDABP Framework两个核心技术,后面还会陆续发布核心构件实现综合案例实现系列文章,敬请关注!

ABP Framework 研习社(QQ群:726299208) 专注 ABP Framework 学习及DDD实施经验分享;示例源码、电子书共享,欢迎加入!

基于ABP落地领域驱动设计-05.实体创建和更新最佳实践相关推荐

  1. 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑

    系列文章 基于ABP落地领域驱动设计-01.全景图 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则 基于ABP落地领域驱动设 ...

  2. 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则

    前言 上一篇 基于ABP落地领域驱动设计-01.全景图 概述了DDD理论和对应的解决方案.项目组成.项目引用关系,以及基于ABP落地DDD的通用原则.从这本篇开始,会更加深入地介绍在基于 ABP Fr ...

  3. 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则

    dotNET兄弟会 专注.Net开源技术及跨平台开发!致力于构建完善的.Net开放技术文库!为.Net爱好者提供学习交流家园! 公众号 围绕DDD和ABP Framework两个核心技术,后面还会陆续 ...

  4. 基于ABP落地领域驱动设计-01.全景图

    什么是领域驱动? 领域驱动设计(简称:DDD)是一种针对复杂需求的软件开发方法.将软件实现与不断发展的模型联系起来,专注于核心领域逻辑,而不是基础设施细节.DDD适用于复杂领域和大规模应用,而不是简单 ...

  5. 基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则

    围绕DDD和ABP Framework两个核心技术,后面还会陆续发布核心构件实现.综合案例实现系列文章,敬请关注! ABP Framework 研习社(QQ群:726299208) ABP Frame ...

  6. 基于花季A传媒ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则

    ​ DDD和ABP Framework两个中心技术,后边还会陆续发布中心构件完成.归纳事例完成系列文章,敬请关注! ABP Framework 学习及实施DDD经历分享:示例源码.电子书同享,欢迎加入 ...

  7. 领域驱动设计在互联网业务开发中的实践

    前言 至少30年以前,一些软件设计人员就已经意识到领域建模和设计的重要性,并形成一种思潮,Eric Evans将其定义为领域驱动设计(Domain-Driven Design,简称DDD).在互联网开 ...

  8. 领域驱动设计在美团点评业务系统的实践

    至少30年以前,一些软件设计人员就已经意识到领域建模和设计的重要性,并形成一种思潮,Eric Evans将其定义为领域驱动设计(Domain-Driven Design,简称DDD).在互联网开发&q ...

  9. 基于DDD(领域驱动设计)的微服务设计实例

    目录 一.战略设计: 1.产品愿景 2.场景分析 3.领域建模 1)提取领域对象 2)构建聚合 3)划分界限上下文 4.微服务拆分 二.战术设计 1.分析微服务领域对象 1)服务识别和设计 2)聚合内 ...

最新文章

  1. lnmp shell安装脚本
  2. Windows Mobile 技术开发黄金周系列课程
  3. 2.6 MYSQL的输出格式
  4. 一文看懂WebTransport
  5. 解决无法删除表,提示被外键约束引用
  6. Hadoop下载和源码阅读
  7. 通过rpm包安装、配置及卸载mysql的详细过程.
  8. vim中实现javascript代码自动完成功能
  9. 期待!华为P40渲染图再曝光:前置打孔双摄+6.5英寸大屏
  10. java版hive的UDF(临时函数与永久函数)
  11. 文本以大写字母html,如何强制EditText以大写字母开始文本?
  12. cookie被淘汰_可爱可恨的 Cookie
  13. Vb6 Modbus TCP通讯示例源代码
  14. VB.Net数据库编程
  15. 人工智能--黑白图片上色
  16. 二值图像连通区域标记matlab,二值图像快速连通区域计算方法与流程
  17. SD卡pin引脚说明及PCB layout指导
  18. IDEA中写代码间距变大报红,报错Cannot resolve symbol
  19. PS做以图片为文字背景
  20. 几个去黑头的土方法,试试看! - 生活至上,美容至尚!

热门文章

  1. r-studio扫描后各种颜色_iPhone手机备忘录,原来还隐藏着扫描仪,你不会还不知道吧?...
  2. [Unity优化]批处理03:静态批处理
  3. windows下共享文件夹在Linux下打开
  4. MongoDB的安装与使用
  5. 我对于全栈工程师的理解
  6. Codeforces Round #410 (Div. 2) D. Mike and distribution 思维+数学
  7. URAL 1682 Crazy Professor (并查集)
  8. 静态html引入js添加随机数后缀防止缓存
  9. VS2010插件之NuGet
  10. 不断电系统的容量如何计算?