18

在阅读了关于 stackoverflow 的一些 Q/As 之后,我仍然对在我的 Web 应用程序中正确实现 DTO 感到困惑。我当前的实现是一个(基于 Java EE)多层架构(具有持久性、服务和表示层),但所有层都使用一个“通用”包,其中包含(以及其他)域对象。在这种情况下,这些层实际上不能被认为是独立的。我打算逐步删除 common 包,但我遇到了各种挑战/问题:

  • 假设持久层将使用类myproject.persistence.domain.UserEntity(一个基于 JPA 的实体)来将数据存储到数据库或从数据库加载数据。为了在视图中显示数据,我将提供另一个类myproject.service.domain.User。我在哪里转换它们?用户的服务是否负责在两个类之间进行转换?这真的有助于改善耦合吗?
  • User类应该是什么样子的?它应该只包含不可变的吸气剂吗?视图编辑现有用户(创建新User,使用现有User对象的 getter 等)会不会很麻烦?
  • 我应该使用相同的 DTO 类(User)向服务发送请求以修改现有用户/创建新用户还是应该实现其他类?
  • 通过使用myproject.service.domain中的所有 DTO,表示层不会非常依赖服务层吗?
  • 如何处理我自己的异常?我当前的方法会重新抛出大多数“严重”异常,直到它们被表示层处理(通常它们被记录并且用户被告知出现问题)。一方面,我有一个问题,我再次拥有一个共享包。另一方面,我仍然不确定这是否可以被视为“最佳实践”。有任何想法吗?

谢谢你的任何答案。

4

2 回答 2

19

在不同的层之间有一些包并不少见,但它通常只用于横切关注点,例如日志记录。您的模型不应由不同的层共享,否则对模型的更改将需要对所有这些层进行更改。通常,您的模型是较低层,靠近数据层(上、下或交织,取决于方法)。

数据传输对象,顾名思义,是用于传输数据的简单类。因此,它们通常用于在层之间进行通信,特别是当您拥有通过消息而不是对象进行通信的 SOA 架构时。DTO 应该是不可变的,因为它们的存在只是为了传输信息,而不是改变它。

你的领域对象是一回事,你的 DTO 是另一回事,而你在表示层中需要的对象又是另一回事。然而,在小型项目中,实现所有这些不同的集合并在它们之间进行转换可能不值得。这仅取决于您的要求。

您正在设计一个 Web 应用程序,但它可能有助于您的设计问自己,“我可以通过桌面应用程序切换我的 Web 应用程序吗?我的服务层真的不知道我的表示逻辑吗?”。以这些术语思考将引导您走向更好的架构。

关于你的问题:

假设持久层将使用类 myproject.persistence.domain.UserEntity(一个基于 JPA 的实体)来将数据存储到数据库或从数据库加载数据。为了在视图中显示数据,我将提供另一个类 myproject.service.domain.User。我在哪里转换它们?用户的服务是否负责在两个类之间进行转换?这真的有助于改善耦合吗?

服务层知道它的类(DTO)和它下面的层(比如说持久性)。所以是的,服务负责在持久性和自身之间进行转换。

User 类应该是什么样子的?它应该只包含不可变的吸气剂吗?视图编辑现有用户(创建新用户、使用现有用户对象的 getter 等)会不会很麻烦?

DTO 背后的想法是您仅将它们用于传输,因此不需要创建新用户等操作。为此,您需要不同的对象。

我应该使用相同的 DTO 类(用户)向服务发送请求以修改现有用户/创建新用户还是应该实现其他类?

服务方法可以表达操作,DTO 是它的参数,只包含数据。另一种选择是使用代表操作并包含 DTO 的命令。这在 SOA 体系结构中很流行,您的服务可能只是一个命令处理器,例如具有一个Execute将接口作为参数的单个操作ICommand(而不是每个命令具有一个操作)。

通过使用 myproject.service.domain 中的所有 DTO,表示层不会非常依赖服务层吗?

是的,服务层之上的层将依赖于它。这就是想法。好处是只有该层依赖于它,没有上层或下层,因此更改只会影响该层(与使用每一层的域类不同)。

如何处理我自己的异常?我当前的方法会重新抛出大多数“严重”异常,直到它们被表示层处理(通常它们被记录并且用户被告知出现问题)。一方面,我有一个问题,我再次拥有一个共享包。另一方面,我仍然不确定这是否可以被视为“最佳实践”。有任何想法吗?

每一层都可以有自己的例外。它们从一层流向另一层,封装成下一种异常。有时,它们将由一个会做某事(例如日志记录)的层处理,然后可能会引发上层必须处理的不同异常。其他时候,它们可能会被处理并且问题可能会得到解决。以连接到数据库的问题为例。它会抛出异常。您可以处理它并决定在一秒钟后重试,然后可能会成功,因此异常不会向上流动。如果重试也失败了,异常将被重新抛出,它可能会一直流到表示层,在那里你可以优雅地通知用户并要求他重试层。

于 2014-04-26T08:28:58.787 回答
5

松散耦合确实是推荐的方法,这意味着您最终会遇到巨大的、编写乏味的、在业务逻辑中维护转换器的痛苦。是的,它们属于业务逻辑:DAO 和视图之间的层。因此,业务层最终将取决于 DAO DTO 和视图 DTO。并且将充满 Converter 类,淡化您对实际业务逻辑的看法......

如果您可以摆脱不可变视图 DTO,那就太好了。您用于序列化它们的库可能需要它们具有设置器。或者,如果它们有二传手,您可能会发现它们更容易构建。

对视图和 DAO 使用相同的 DTO 类,我已经很好了。这很糟糕,但老实说,我并没有觉得系统会更加解耦,因为业务逻辑,最重要的部分,无论如何都必须依赖于一切。这种紧密耦合提供了极大的简洁性,并使视图和 DAO 层的同步变得更加容易。通过使用合成,我仍然可以拥有特定于其中一层的东西,而在另一层中看不到。

最后,关于异常。最外层,即视图层(如果使用 Spring,则为控制器)的职责是捕获从内层传播的错误,无论是使用异常,还是使用特殊的 DTO 字段。然后这个最外层需要决定是否将错误通知客户端,以及如何通知。事实上,直到最内层,您需要区分最外层需要处理的不同类型的错误。例如,如果 DAO 层发生了什么事,并且视图层需要知道返回 400 还是 500,DAO 层将需要向视图层提供决定使用哪一个所需的信息,而这些信息将需要通过所有中间层,谁应该能够添加自己的错误和错误类型。将 IOException 或 SQLException 传播到最外层是不够的,内层还需要告诉外层这是否是预期错误。悲伤但真实。

于 2014-04-26T08:22:17.570 回答