Java 最佳实践 - 多层架构和 DTO

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23308241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 21:48:16  来源:igfitidea点击:

Best Practice - Multi Layer Architecture and DTOs

javajpadtomulti-tier

提问by nils

After reading some of the Q/As here on stackoverflow, I am still confused about the correct implementation of DTOs in my web application. My current implementation is a (Java EE based) multi-tier architecture (with persistence, service and presentation layer) but with a "common" package used by all layers, containing (amongst others) domain objecs. In this case the layers can not really be considered as independent. I am planning to remove the common package step by step, but I encounter various challenges/questions:

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

  • Assume the persistence layer would use a class myproject.persistence.domain.UserEntity(a JPA based entity) to store and load data to/from the database. To show data in the view I would provide another class myproject.service.domain.User. Where do I convert them? Would the service for the users be responsible to convert between the two classes? Would this really help to improve the coupling?
  • How should the Userclass look like? Should it contain only getters to be immutable? Wouldn't it be cumbersome for the views to edit existing users (create a new User, use the getters of the existing Userobject etc.)?
  • Should I use the same DTO-classes (User) to send a request to the service to modify an existing user/create a new user or should I implement other classes?
  • Wouldn't the presentation layer be very dependent on the service layer by using all the DTOs in myproject.service.domain?
  • How to handle my own exceptions? My current approach rethrows most "severe" exceptions until they are handled by the presentation layer (usually they are logged and the user is informed that something went wrong). On the one hand I have the problem that I hava again a shared package. On the other hand I am still not sure that this can be considered "best practice". Any ideas?
  • 假设持久层将使用类myproject.persistence.domain.UserEntity(一个基于 JPA 的实体)来存储和加载数据到/从数据库。为了在视图中显示数据,我将提供另一个类myproject.service.domain.User。我在哪里转换它们?用户服务是否负责在两个类之间进行转换?这真的有助于改善耦合吗?
  • User类应该是什么样子的?它应该只包含不可变的吸气剂吗?视图编辑现有用户(创建新User,使用现有User对象的 getter等)会不会很麻烦?
  • 我应该使用相同的 DTO 类(User)向服务发送请求以修改现有用户/创建新用户还是应该实现其他类?
  • 通过使用myproject.service.domain 中的所有 DTO,表示层不会非常依赖于服务层吗?
  • 如何处理我自己的异常?我当前的方法会重新抛出大多数“严重”异常,直到它们被表示层处理(通常它们会被记录下来并且用户被告知出现问题)。一方面,我有一个问题,我再次拥有一个共享包。另一方面,我仍然不确定这是否可以被视为“最佳实践”。有任何想法吗?

Thank you for any answers.

感谢您提供任何答案。

采纳答案by jnovo

Having some packages among different layers is not uncommon, however it is usually done only for cross-cutting concerns such as logging. Your model should not be shared by different layers, or changes to the model would require changes in all those layers. Typically, your model is a lower layer, close to data layer (over, under, or intertwined, depending on the approach).

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

Data Transfer Objects, as their name imply, are simple classes used to transfer data. As such, they are usually used to communicate between layers, specially when you have a SOA architecture which communicates through messages and not objects. DTOs should be immutable since they merely exist for the purpose of transferring information, not altering it.

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

Your domain objects are one thing, your DTOs are a different thing, and the objects you need in your presentation layer are yet another thing. However, in small projects it may not be worth the effort of implementing all those different sets and converting between them. That just depends on your requirements.

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

You are designing a web application but it may help your design to ask yourself, "could I switch my web application by a desktop application? Is my service layer really unaware of my presentation logic?". Thinking in these terms will guide you towards a better architecture.

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

On to your questions:

关于你的问题:

Assume the persistence layer would use a class myproject.persistence.domain.UserEntity (a JPA based entity) to store and load data to/from the database. To show data in the view I would provide another class myproject.service.domain.User. Where do I convert them? Would the service for the users be responsible to convert between the two classes? Would this really help to improve the coupling?

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

The service layer knows its classes (DTOs) and the layer below it (let's say persistence). So yes, the service is responsible for translating between persistence and itself.

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

How should the User class look like? Should it contain only getters to be immutable? Wouldn't it be cumbersome for the views to edit existing users (create a new User, use the getters of the existing User object etc.)?

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

The idea behind DTOs is that you only use them for transfer, so operations like creating a new user are not required. For that you need different objects.

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

Should I use the same DTO-classes (User) to send a request to the service to modify an existing user/create a new user or should I implement other classes?

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

The service methods might express the operation, the DTOs being its parameters containing just the data. Another option is using commands which represent the operation and also contain the DTOs. This is popular in SOA architectures where your service may be a mere command processor for instance having one single Executeoperation taking a ICommandinterface as parameter (as opposed to having one operation per command).

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

Wouldn't the presentation layer be very dependent on the service layer by using all the DTOs in myproject.service.domain?

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

Yes, the layer over the service layer will be dependent on it. That is the idea. The upside is that only that layer is dependent on it, no upper or lower layers so changes only affect that layer (unlike what happens if you use your domain classes from every layer).

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

How to handle my own exceptions? My current approach rethrows most "severe" exceptions until they are handled by the presentation layer (usually they are logged and the user is informed that something went wrong). On the one hand I have the problem that I hava again a shared package. On the other hand I am still not sure that this can be considered "best practice". Any ideas?

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

Each layer can have its own exceptions. They flow from one layer to another encapsulated into the next kind of exception. Sometimes, they will be handled by one layer which will do something (logging, for instance) and maybe then throw a different exception that an upper layer must handle. Other times, they might be handled and the problem might be solved. Think for instance of a problem connecting to the database. It would throw an exception. You could handle it and decide to retry after a second and maybe then there is success, thus the exception would not flow upwards. Should the retry also fail, the exception would be re-thrown and it may flow all the way up to the presentation layer where you gracefully notify the user and ask him to retry layer.

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

回答by yotsov

Loose coupling is indeed the recommended way to go, which means you will end up with huge, boring to write, painful to maintain converters in your business logic. Yes, they belong in the business logic: the layer between the DAOs and the views. So the business layer will end up depending on both the DAO DTOs and the view DTOs. And will be full of Converter classes, diluting your view of the actual business logic...

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

If you can get away with having immutable view DTOs, that's great. A library you use for serializing them might require them to have setters though. Or you might find them easier to build if they have setters.

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

I have gotten away just fine with using the same DTO classes for both the views and the DAOs. It is bad, but honestly, I did not have the feeling that the system was more decoupled otherwise, since business logic, the most essential part, has to depend on everything anyway. This tight coupling provided for great conciseness, and made it easier to sync the view and DAO layers. I could still have things specific just to one of the layers and not seen in the other by using composition.

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

Finally, regarding exceptions. It is a responsibility of the outermost layer, the view layer (the Controllers if you are using Spring) to catch errors propagated from the inner layers be it using exceptions, be it using special DTO fields. Then this outermost layer needs to decide if to inform the client of the error, and how. The fact is that down to the innermost layer, you need to distinguish between the different types of errors that the outermost layer will need to handle. For example if something happens in the DAO layer, and the view layer needs to know if to return 400 or 500, the DAO layer will need to provide the view layer with the information needed to decide which one to use, and this information will need to pass through all intermediary levels, who should be able to add their own errors and error types. Propagating an IOException or SQLException to the outermost layer is not enough, the inner layer needs to also tell the outer layer if this is an expected error or not. Sad but true.

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