最佳实践 - 多层体系结构和 DTO [已关闭]

2022-09-02 21:24:32

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

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

感谢您的任何回答。


答案 1

在不同层之间拥有一些包并不罕见,但是通常仅用于横切问题,例如日志记录。您的模型不应由不同的层共享,否则对模型的更改将需要更改所有这些层。通常,模型是靠近数据层的下层(上方、下方或交织在一起,具体取决于方法)。

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

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

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

关于您的问题:

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

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

用户类的外观应该如何?它应该只包含不可变的 getter 吗?视图编辑现有用户(创建新用户,使用现有 User 对象的 getter 等)不是很麻烦吗?

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

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

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

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

是的,服务层上的层将依赖于它。这就是我们的想法。好处是只有该层依赖于它,没有上层或下层,因此更改仅影响该层(与从每个层使用域类时发生的情况不同)。

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

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


答案 2

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

如果您能够摆脱不可变视图 DTO,那就太好了。但是,用于序列化它们的库可能要求它们具有 setter。或者,如果它们具有 setter,您可能会发现它们更容易构建。

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

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


推荐