4.7 Data transfer Objects 数据传输对象

Data transfer Objects 数据传输对象

A DTO is a simple object that is used to transfer state (data) between the Application and Presentation Layers. So,Application Service methods gets and returns DTOs.
DTO是一个简单的对象,用于在应用层和表现层之间传输状态(数据)。因此,应用服务方法获取和返回DTO。

Common DTO Principles & Best Practices DTO一般原则和最佳实践

  • A DTO should be serializable, by its nature. Because,most of the time it is transferred over network. So, it should have a parameterless (empty) constructor.
  • 从本质上讲,DTO应该是可序列化的。因为,大部分时间它都是通过网络传输的。所以,它应该有一个无参数(空)构造函数
  • Should not contain any business logic.
  • 不应该包含任何业务逻辑
  • Never inherit from or reference to entities.
  • 永远不要继承或引用实体

Input DTOs (those are passed to the Application Service methods) have different natures than Output DTOs (those are returned from the Application Service methods). So, they will be treated differently.
输入DTO(指那些被传递给应用服务方法的DTO)与输出DTO(指那些从应用服务方法返回的DTO)有本质上的差异。因此,它们将被区别对待。

Input DTO Best Practices 输入DTO最佳实践

Do not Define Unused Properties for Input DTOs 不要为输入DTO定义无用的属性

Define only the properties needed for the use case! Otherwise,it will be confusing for the clients to use the Application Service method. You can surely define optional properties, but they should effect how the use case is working, when the client provides them.
只定义用例所需的属性! 否则,客户端在使用应用服务方法时就会感到困惑。你当然可以定义可选的属性,但当客户端提供这些属性时,它们可能会影响用例的工作方式。

This rule seems unnecessary first. Who would define unused parameters (input DTO properties) for a method? But it happens, especially when you try to reuse input DTOs.
这条规则首先似乎没有必要。谁会为一个方法定义未使用的参数(输入DTO属性)?但这种情况经常发生,特别是当你试图重用输入DTO的时候。

Do not Re-Use Input DTOs 不要重复使用输入DTO

Define a specialized input DTO for each use case (Application Service method). Otherwise, some properties are not used in some cases and this violates the rule defined above: Do not Define Unused Properties for Input DTOs.
为每个用例(应用服务方法)定义一个特有的输入DTO。否则,一些属性在某些情况下不会被使用,这就违反了上面定义的规则:不要为输入DTO定义无用的属性。

Sometimes, it seems appealing to reuse the same DTO class for two use cases, because they are almost same. Even if they are same now, they will probably become different by the time and you will come to the same problem. Code duplication is a better practice than coupling use cases.
有时,在两个用例中重复使用同一个DTO类似乎很有吸引力,因为它们几乎是一样的。即使它们现在是一样的,随着时间的变化它们可能会变得不同,你会遇到同样的问题。重复代码与用例耦合相比来说是一种更好的做法。

Another way of reusing input DTOs is inheriting DTOs from each other. While this can be useful in some rare cases, most of the time it brings you to the same point.
重用输入DTO的另一种方式是相互继承DTO。虽然这在某些罕见的情况下是有用的,但大多数时候它带给你的是同样的问题。

Example: User Application Service 示例:用户应用服务

image

IUserAppService uses UserDto as the input DTO in all methods (use cases). UserDto is defined below:
IUserAppService在所有方法(用例)中使用UserDto作为输入DTO。UserDto定义如下:

image

For this example; 就这个例子而言:

  • Id is not used in Create since the server determines it.
  • Id在Create方法中没有使用,因为它由服务端生成。
  • Password is not used in Update since we have another method for it.
  • Password在更新中没有使用,因为我们修改密码用另一种方法。
  • CreationTime is never used since we can't allow client to send the Creation Time. It should be set in the server.
  • CreationTime从来都不会用到,因为我们不允许客户端发送创建时间。它应该在服务端中设置。

A true implementation can be like that: 真正的实现可以是这样的:

image

With the given input DTO classes: 用到的输入DTO类:

image

This is more maintainable approach although more code is written.
这是更便于维护的方法,尽管要写更多的代码。

Exceptional Case: There can be some exceptions for this rule: If you always want to develop two methods in parallel, they may share the same input DTO (by inheritance or direct reuse). For example, if you have a reporting page that has some filters and you have multiple Application Service methods (like screen report, excel report and csv report methods) use the same filters but returns different results, you may want to reuse the same filter input DTO to couple these use cases. Because, in this example, whenever you change a filter, you have to make the necessary changes in all the methods to have a consistent reporting system.
例外情况:这条规则也可能有一些例外:如果你总是想并行开发两个方法,它们可能共享相同的输入DTO(通过继承或直接重用)。例如,如果你有一个有一些过滤条件的报表页面,你有多个应用服务方法(如显示屏看板、excel报表和csv报表方法)使用相同的过滤条件,但返回不同的结果,你可能想重用相同的带过滤条件输入DTO来复用这些用例。因为,在这个例子中,每当你改变一个过滤条件时,你必须在所有的方法中进行必要的改变,以拥有一个一致的报表系统。

Input DTO Validation Logic 输入DTO校验逻辑

  • Implement only formal validation inside the DTO. Use Data Annotation Validation Attributes or implement IValidatableObject for formal validation.
  • 在DTO内部只实现表象验证。使用数据注解验证属性或实现IValidatableObject进行表象验证。
  • Do not perform domain validation. For example, don't try to check unique username constraint in the DTOs.
  • 不要进行领域验证。例如,不要试图在DTO中检查唯一的用户名约束。
Example: Using Data Annotation Attributes 示例:使用数据注解属性

image

ABP Framework automatically validates input DTOs, throws AbpValidationException and returns HTTP Status 400 to the client in case of an invalid input.
ABP框架自动验证输入的DTO,抛出AbpValidationException,并在输入无效的情况下向客户端返回HTTP状态码400

Some developers think it is better to separate the validation rules and DTO classes. We think the declarative (Data Annotation) approach is practical and useful and doesn't cause any design problem. However, ABP also supports FluentValidation integration if you prefer the other approach.
一些开发者认为将验证规则和DTO类分开更好。我们认为声明式(Data Annotation)的方法是实用的的和有用的,不会引起任何设计问题。然而,如果你喜欢其他的方法,ABP也支持FluentValidation集成。

See the Validation document for all validation options.
想了解所有验证选项参见验证文档。

Output DTO Best Practices 输出DTO的最佳实践

  • Keep output DTO count minimum. Reuse where possible (exception: Do not reuse input DTOs as output DTOs).
  • 保持输出DTO数量最少。尽可能地重复使用(例外:不要将输入DTO作为输出DTO重复使用)。
  • Output DTOs can contain more properties than used in the client code.
  • 输出DTOs可以包含比在运用于客户端代码中更多的属性。
  • Return entity DTO from Create and Update methods.
  • 从创建和更新方法中返回实体DTO。

The main goals of these suggestions are; 这些建议的主要目的是:

  • Make client code easy to develop and extend;
  • 使客户端代码易于开发和扩展;
    • Dealing with similar, but not same DTOs are problematic on the client side.
    • 处理类似但不相同的DTO在客户端是有问题的。
    • It is common to need to other properties on the UI/client in the future. Returning all properties (by considering security and privileges) of an entity makes client code easy to improve without requiring to touch to the backend code.
    • 将来,在用户界面/客户端上需要用到其他属性是很常见的。返回一个实体的所有属性(鉴于安全和权限方面的考虑),使客户端代码易于改进,且改动无需触及后台代码。
    • If you are opening your API to 3rd-party clients that you don't know requirements of each client.
    • 如果你向第三方客户开放你的API,你就不知道每个客户的需求。
  • Make the server side code easy to develop and extend;
  • 使服务器端的代码易于开发和扩展;
    • You have less class to understand and maintain.
    • 你用更少的类便于理解和维护。
    • You can reuse the Entity->DTO object mapping code.
    • 你可以重复使用Entity->DTO对象映射代码。
    • Returning same types from different methods make it easy and clear to create new methods.
    • 从不同的方法中返回相同的类型,使其在创建新的方法上变得简单而明了。
Example: Returning Different DTOs from different methods 示例:从不同的方法返回不同的DTO

image

(We didn't use async methods to make the example cleaner, but use async in your real world application!)
(我们没有使用异步方法来使这个例子更简洁,但在你的实际应用程序中要使用异步!)

The example code above returns different DTO types for each method. As you can guess, there will be a lot of code duplications for querying data, mapping entities to DTOs.
上面的示例代码为每个方法返回不同的DTO类型。你可以猜到,在查询数据、将实体映射到DTO方面会有大量的重复代码。

The IUserAppService service above can be simplified: 上面的IUserAppService服务可以被简化:

image

With a single output DTO: 使用单一的输出DTO:

image

  • Removed GetUserNameAndEmail and GetRoles since Get method already returns the necessary information.
  • 删除了GetUserNameAndEmailGetRoles,因为Get方法已经返回必要的信息。
  • GetList now returns the same with Get.
  • 现在GetList的返回的数据类型与Get相同。
  • Create and Update also returns the same UserDto.
  • CreateUpdate也返回相同的UserDto

Using the same DTO has a lot of advantages as explained before. For example, think a scenario where you show a data grid of Users on the UI. After updating a user, you can get the return value and update it on the UI. So, you don't need to call GetList again. This is why we suggest to return the entity DTO (UserDto here) as return value from the Create and Update operations.
如上所示,使用相同的DTO有很多优点。例如,考虑一个场景,你在用户界面上显示一个用户的数据表格。在更新一个用户后,你可以得到返回值并在用户界面上更新它。所以,你不需要再次调用GetList。这就是为什么我们建议将实体DTO(这里指UserDto)作为 CreateUpdate操作的返回值。

Discussion 讨论

Some of the output DTO suggestions may not fit in every scenario. These suggestions can be ignored for performance reasons, especially when large data sets returned or when you create services for your own UI and you have too many concurrent requests.
一些输出DTO的建议可能不适用于每一种情况。出于性能方面的考虑,这些建议可以被忽略,特别是当返回大量数据集合时,或者当你为自己的用户界面创建服务时,你有太多的并发请求。

In these cases, you may want to create specialized output DTOs with minimal information. The suggestions above are especially for applications where maintaining the codebase is more important than negligible performance lost.
在这些情况下,你可能想创建包含最小信息量的专门的输出DTO。上面的建议特别适用于维护代码库比可忽略不计的性能损失更重要的应用程序。

Object to Object Mapping 对象间的映射

Automatic object to object mapping is a useful approach to copy values from one object to another when two objects have same or similar properties.
当两个对象具有相同或相似的属性时,对象间的自动映射是一种可以将数值从一个对象复制到另一个对象有用的方法。

DTO and Entity classes generally have same/similar properties and you typically need to create DTO objects from Entities. ABP's object to object mapping system with AutoMapper integration makes these operations much easier comparing to manual mapping.
DTO和Entity类通常有相同/相似的属性,你通常需要从Entity对象创建DTO对象。ABP的对象间的映射系统AutoMapper集成使这些操作比手动映射容易得多。

  • Use auto object mapping only for Entity to output DTO mappings.
  • 只对实体到输出DTO的映射使用自动对象映射。
  • Do not use auto object mapping for input DTO to Entity mappings.
  • 在输入DTO到实体的映射中不要使用自动对象映射。

There are some reasons why you should not use input DTO to Entity auto mapping;
你不应该使用输入DTO到实体的自动映射,有如下原因:

  1. An Entity class typically has a constructor that takes parameters and ensures valid object creation. Auto object mapping operation generally requires an empty constructor.
  2. 一个实体类通常有一个构造函数,它接受参数并确保有效的创建对象。自动对象映射操作一般需要一个空的构造函数。
  3. Most of the entity properties will have private setters and you should use methods to change these properties in a controlled way.
  4. 大多数实体属性会拥有私有设置器,你应该使用方法以可控的方式来改变这些属性。
  5. You typically need to carefully validate and process the user/client input rather than blindly mapping to the entity properties.
  6. 你通常需要仔细验证和处理用户/客户端的输入,而不是盲目地映射到实体的属性上。

While some of these problems can be solved through mapping configurations (For example, AutoMapper allows to define custom mapping rules), it makes your business code implicit/hidden and tightly coupled to the infrastructure. We think the business code should be explicit, clear and easy to understand.
虽然其中一些问题可以通过映射配置来解决(例如,AutoMapper允许定义自定义映射规则),但它使你的业务代码更隐僻并与基础设施紧密耦合。我们认为业务代码应该是明确的、清晰的、容易理解的。

See the Entity Creation section below for an example implementation of the suggestions made in this section.
关于本节提出的建议的实行示例,请参见下面的实体创建部分。

原文地址:https://www.cnblogs.com/tjubuntu/p/15674766.html