最佳实践 - 多层架构和DTO

时间:2014-04-26 07:53:00

标签: java jpa dto multi-tier

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

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

感谢您的回答。

2 个答案:

答案 0 :(得分:14)

在不同层之间包含一些包并不罕见,但通常只针对日志记录等交叉问题。您的模型不应由不同的图层共享,或者对模型的更改需要在所有图层中进行更改。通常,您的模型是较低层,靠近数据层(上,下或交织,取决于方法)。

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

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

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

关于你的问题:

  

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

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

  

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

DTO背后的想法是您只使用它们进行转移,因此不需要创建新用户等操作。为此你需要不同的对象。

  

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

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

  

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

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

  

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

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

答案 1 :(得分:3)

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

如果您可以使用不可变的视图DTO,那就太棒了。用于序列化它们的库可能要求它们具有setter。或者,如果他们有制定者,你可能会发现它们更容易构建。

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

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

相关问题