DTOs:最佳实践

33

我考虑使用DTO代替传递我的领域对象。我已经在这里和其他地方读了几篇文章,我知道有几种方法可以完成这个过程。

如果我总共只有大约10个领域类,并且考虑到我想在我的视图(WPF前端)中使用DTO而不是领域对象,那么推荐的方法是什么。 我认为在我的情况下使用像automapper之类的工具可能会过度kill,因此我想编写自己的mapper类,该类将具有用于将领域类型转换为DTO类型的方法。

如何做到这一点最好的方式是什么,是否有任何样例可以让我开始做这个?

第二个问题:在编写创建DTO的方法时,如何处理设置所有数据,特别是当领域类型引用其他领域对象时?为映射到领域类中的那些引用类型编写DTO中的等效属性吗? 如果我没有正确表达我的第二个问题,请问。但我认为你明白我试图询问什么。

第三个问题:在编写DTO时,我应该编写多个DTO,每个DTO都包含给定模型的部分数据,以便可以用来满足特定视图的要求,还是DTO应该具有对应的模型类中所有数据。


请准备为特定的服务方法编写多个特定的数据传输对象,不仅仅是为特定的域模型编写。 - Lightman
7个回答

15

我在这里阅读了一些有关DTO的帖子,似乎很多人认为它们等同于我所认为的ViewModel。 DTO就是数据传输对象 - 它是通过网络传输的内容。因此,我有一个网站和服务,只有服务才能访问实际的领域/实体对象,并返回DTO。这些可能是1:1映射的,但请考虑DTO可能是从另一个服务调用、数据库查询、读取配置文件等填充的。

之后,网站可以接收这些DTO并将其添加到ViewModel中,或者转换成ViewModel。该ViewModel可能包含许多不同类型的DTO。一个简单的例子是任务管理器 - ViewModel包含正在编辑的任务对象以及一组可以分配任务的Dto.User对象。

请记住,返回DTO的服务可能被网站、平板电脑或手机应用程序使用。这些应用程序将具有不同的视图以利用其显示效果,因此ViewModel将不同,但DTO将保持不变。

无论如何,我喜欢这些类型的讨论,所以请让我知道您的想法。

Matt


1
只是为了澄清,DTO不是ViewModel。它不是DisplayModel,而是一个与UI无关的TransferModel。更重要的是,当您执行REST服务传输DTO时,它们不应该知道UI结构。 - Elisabeth

4

我在一个项目中正在使用DTOs。我倾向于只创建展示特定视图所需数据的DTOs。我在我的数据访问类中获取所有在视图中显示的数据。例如,我可能会有一个Order对象,它引用了一个Client对象:

public class Client{
    public int Id{get;set;}
    public string Name{get;set;}
}

public class Order{
    public int OrderID{get;set;}
    public Client client{get;set;}
    public double Total{get;set;}
    public IEnumerable<OrderLine> lines {get;set;}
}

那么在我的 OrderListDTO 中,我可能会有这样的内容:

public class OrderListDTO{
    public int OrderId{get;set;}
    public string ClientName{get;set;}
    ...
 }

我想在视图中显示哪些字段。我在数据库访问代码中获取了所有这些字段,因此不需要在视图或控制器代码中处理实体关联。


你如何处理DTO对象中的“lines”属性?你会将OrderListDTO扁平化还是以某种方式加载“lines”集合? - PPC-Coder
根据上下文而定。如果我需要在视图中显示这些行,我会加载它们;否则就不会。有时候我的OrderListDTO上可能会有一个LineCount属性,我会执行LineCount=order.lines.Count(),或者我会显示一个总数:LineSum=order.lines.Sum(t=>t.Quantity)... - Carles Company

3

最佳开发DTO的方式

开始开发DTO的方法是要理解它们的唯一目的是将业务实体的子集数据传输给不同的客户端(可以是UI或外部服务)。在这种理解下,您可以为每个客户端创建单独的包...并编写DTO类。对于映射,您可以编写自己的映射器,定义要传递给工厂的接口,根据这些接口创建DTO对象,该对象基于正在创建DTO的实体中提取数据。您还可以定义要放置在实体字段上的注释,但就我个人而言,我更喜欢使用接口方式,因为使用的注释数量太多。关于DTO的主要注意事项是它们也是类,DTO之间的数据应该被重用,换句话说,虽然为每个用例创建DTO可能很诱人,但要尽量重用现有的DTO来最小化其数量。

入门指南

关于入门,如上所述,DTO的唯一目的是为客户端提供所需的数据,因此您可以考虑只使用setter将数据设置到DTO中...或者定义一个从接口到实体的工厂,根据接口从实体创建DTO......

关于您的第三个问题,请根据客户端的要求执行 :)


1

我参与了一个使用 spring-jdbc 的项目,其中使用了 DAO 层。有时现有的实体并不能涵盖数据库中所有可能的数据。因此,我开始使用 DTO

通过应用“70结构编程规则”,我将所有DTO放入单独的包中:

package com.evil.dao;     // DAO interfaces for IOC.
package com.evil.dao.impl; // DAO implementation classes.
package com.evil.dao.dto; // DTOs

现在我重新思考并决定将所有没有重用的结果集DTO作为DAO接口上的内部类。所以DAO接口看起来像这样:

interface StatisticDao {
    class StatisticDto {
        int count;
        double amount;
        String type;

        public static void extract(ResultSet rs, StatisticDto dto) { ... }
    }

    List<StatisticDto> getStatistic(Criteria criteria);
}


class StatisticDaoImpl implements StatisticDao {
    List<StatisticDto> getStatistic(Criteria criteria) {
        ...
        RowCallbackHandler callback = new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet rs) throws SQLException {
                StatisticDao.StatisticDto.extract(rs, dto);
                // make action on dto
            }
        }
        namedTemplate.query(query, queryParams, callback);
    }
}

我认为将相关数据放在一起(使用自定义DTO和DAO接口)可以使代码更适合PageUp/PageDown


1
问题1:如果您需要传输的DTO仅是您域对象的简单子集,则可以使用模型映射器避免在代码库中填充逻辑映射。但是,如果您需要对映射应用一些逻辑/转换,则自己实现。

问题2:您可以并且可能应该为每个主DTO上的域对象创建一个DTO。DTO可以包含多个DTO,在其中为您需要映射的每个域对象创建一个DTO。要进行映射,您可以自己完成或甚至使用一些模型映射器。

问题3:如果视图不需要全部暴露您的域,请不要全部暴露。此外,您无需为每个视图创建DTO,请尝试创建公开所需内容并可重复使用以避免具有共享大量信息的多个DTO。但是这主要取决于您的应用程序需求。

如果您需要澄清,请询问。


0

我假设你的领域模型对象有一个主键ID,它可能对应于它们来自的数据库或存储中的ID。

如果以上内容属实,则你的DTO将克服类型引用其他DTO的问题,就像你的领域对象一样,以外键ID的形式存在。因此,领域对象上的OrderLine.OrderHeader关系将在DTO中表示为OrderLine.OrderHeaderId。

希望这可以帮到你。

我可以问一下,为什么你选择在视图中使用DTO而不是你丰富的领域对象吗?


2
DTO中可以有ID属性吗?例如您的示例中的OrderlineID。我认为DTO是完全自包含的数据对象,不会引用数据库和其他外部依赖项。至于为什么要使用DTO,我的项目将来会发展成一个大型系统,我希望现在构建它以遵循将来能够通过Web服务请求等公开数据的能力。从第0天开始遵循良好的实践更好。您对我刚刚添加的第三个问题有什么想法吗? - LiveDotNet

0
我们都知道什么是Dtos(可能)。但重要的是是否过度使用DTO。
在“本地”服务之间使用Dtos传输数据是一种好的实践,但会给开发团队带来巨大的负担。
以下是一些事实:
1.客户端不应查看或与实体(Daos)交互。因此,您始终需要使用DTO将数据传输到/从远程(进程外)。 2.使用Dtos在服务之间传递数据是可选的。如果您不打算将项目分解为微服务,则没有必要这样做。这只会增加负担。
并且这是我的评论:如果您计划在未来长期将项目分布到微服务中,请勿过度使用DTO,或者根本不打算这样做。
您需要阅读这篇文章https://martinfowler.com/bliki/LocalDTO.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接