服务应该总是返回dto,还是也可以返回域模型?

我正在(重新)设计大型应用程序,我们使用基于DDD的多层架构。

我们有带有数据层(存储库的实现)、领域层(域模型和接口的定义——存储库、服务、工作单元)、服务层(服务的实现)的MVC。到目前为止,我们在所有层上使用域模型(主要是实体),并且仅将dto用作视图模型(在控制器中,服务返回域模型,控制器创建视图模型,并将其传递给视图)。

我读过无数关于使用、不使用、映射和传递dto的文章。我知道没有任何明确的答案,但我不确定是否可以将域模型从服务返回到控制器。如果我返回域模型,它仍然没有传递到视图,因为控制器总是创建特定于视图的视图模型-在这种情况下,它似乎是合法的。另一方面,当域模型离开业务层(服务层)时,感觉就不对了。有时服务需要返回域中未定义的数据对象,然后我们必须向未映射的域添加新对象,或者创建POCO对象(这很难看,因为有些服务返回域模型,有些则有效地返回dto)。

问题是——如果我们严格使用视图模型,是否可以将域模型一直返回到控制器,或者我们应该始终使用dto与服务层通信?如果是,是否可以根据服务需求调整域模型?(坦白地说,我不这么认为,因为服务应该消费域拥有的东西。)如果我们应该严格遵循dto,那么它们应该在服务层定义吗?(我想是的。)有时很明显我们应该使用DTO(例如,当服务执行大量业务逻辑并创建新对象时),有时很明显我们应该只使用域模型(例如,当会员服务返回贫血的用户时——创建与域模型相同的DTO似乎没有多大意义)——但我更喜欢一致性和良好实践。

文章域vs DTO vs ViewModel -如何以及何时使用它们?(以及其他一些文章)与我的问题非常相似,但它没有回答这个问题。文章我应该用EF在存储库模式中实现dto吗?也类似,但它不处理DDD。

免责声明:我不打算使用任何设计模式只是因为它的存在和花哨,另一方面,我想使用好的设计模式和实践,也是因为它有助于设计应用程序作为一个整体,有助于分离关注点,即使使用特定的模式不是“必要的”,至少在目前。

90925 次浏览

我建议分析这两个问题:

  1. 你的上层(即视图&视图模型/控制器)以不同的方式消费数据的领域层公开?如果有很多映射,甚至涉及到逻辑,我会建议重新审视你的设计:它可能应该更接近数据的实际使用方式。

  2. 你有多大可能深刻改变你的上层?(例如,将ASP。NET for WPF)。如果这与您的体系结构非常不同,并且您的体系结构不是非常复杂,那么您最好尽可能多地公开域实体。

恐怕这是一个相当广泛的话题,它实际上涉及到您的系统有多复杂及其需求。

似乎您的应用程序足够大和复杂,因为您决定采用DDD方法。 不要在服务层中返回poco实体或所谓的域实体和值对象。如果你想这样做,那么删除你的服务层,因为你不再需要它了!视图模型或数据传输对象应该位于服务层,因为它们应该映射到域模型成员,反之亦然。 那么为什么需要DTO呢?在场景较多的复杂应用中,需要将域的关注点和表示视图分离,一个域模型可以分解为多个DTO,也可以将多个域模型分解为一个DTO。所以最好在分层架构中创建你的DTO,即使它与你的模型相同

我们应该总是使用dto与服务层通信吗? 是的,你必须通过你的服务层返回DTO,因为你必须在服务层中与你的存储库与域模型成员对话,并将它们映射到DTO并返回到MVC控制器,反之亦然

是否可以根据服务需求调整域模型? 服务只是与存储库、域方法和域服务对话,你应该根据你的需求来解决你域中的业务,而不是告诉域需要什么

如果我们应该严格遵循dto,那么它们应该在服务层定义吗?是的,稍后尝试使用DTO或ViewModel,因为它们应该映射到服务层中的域成员,并且在应用程序的控制器中放置DTO不是一个好主意(尝试在服务层中使用请求响应模式),干杯!

到目前为止,我们在所有层上使用域模型(主要是实体),并且仅将dto用作视图模型(在控制器中,服务返回域模型,控制器创建视图模型,并将其传递给视图)。

由于域模型为整个应用程序提供了术语(无处不在的语言),因此最好广泛使用域模型。

使用ViewModels/ dto的唯一原因是在应用程序中实现MVC模式来分离View(任何类型的表示层)和Model(域模型)。在这种情况下,您的表示和域模型是松散耦合的。

有时服务需要返回域中未定义的数据对象,然后我们必须向未映射的域添加新对象,或者创建POCO对象(这很难看,因为有些服务返回域模型,有些则有效地返回dto)。

我假设您谈论的是应用程序/业务/域逻辑服务。

我建议您尽可能地返回域实体。如果需要返回额外的信息,返回包含多个域实体的DTO是可以接受的。

有时,使用第三方框架的人,在域实体上生成代理,会面临从他们的服务中暴露域实体的困难,但这只是错误使用的问题。

问题是——如果我们严格使用视图模型,是否可以将域模型一直返回到控制器,或者我们应该始终使用dto与服务层通信?

我想说在99.9%的情况下返回域实体就足够了。

为了简化dto的创建并将域实体映射到dto中,可以使用AutoMapper

当域模型离开业务层(服务层)时,感觉不对

让你觉得你在把内脏掏出来,对吧?根据Martin Fowler的说法:服务层定义了应用程序的边界,它封装了域。换句话说,它保护了定义域。

有时服务需要返回域中未定义的数据对象

您能否提供此数据对象的示例?

如果我们应该严格遵循dto,那么它们应该在服务层定义吗?

是的,因为响应是服务层的一部分。如果它被定义为“某处”;然后服务层需要引用“其他地方”,为您的千层面添加一个新层。

是否可以将域模型一直返回到控制器,或者我们应该始终使用dto与服务层通信?

DTO是一个响应/请求对象,如果您使用它进行通信,那么它是有意义的。如果你在表示层(MVC-Controllers/View, WebForms, ConsoleApp)中使用域模型,那么表示层与你的域紧密耦合,域中的任何变化都需要你改变控制器。

创建与域模型相同的DTO似乎没有多大意义)

这是DTO的缺点之一。现在,你想到的是代码复制,但随着项目的扩展,它会更有意义,特别是在团队环境中,不同的团队被分配到不同的层。

DTO可能会给应用程序增加额外的复杂性,但层也是如此。DTO是系统的一个昂贵功能,它们不是免费的。

为什么使用DTO

本文提供了使用DTO http://guntherpopp.blogspot.com/2010/09/to-dto-or-not-to-dto.html的优点和缺点

总结如下:

何时使用

  • 用于大型项目。
  • 项目寿命10年以上。
  • 战略,关键任务应用。
  • 大型团队(5人以上)
  • 开发人员在地理上分布。
  • 域和表示是不同的。
  • 减少开销数据交换(DTO的最初目的)

何时不使用

  • 小型至中型项目(最多5人)
  • 项目生命周期为2年左右。
  • GUI、后台等没有单独的团队。

反对DTO的理由

DTO参数

根据我的经验,你应该做些实际的事情。“最好的设计是最简单的设计”——爱因斯坦。有了这样的思想……

如果我们严格使用视图模型,是否可以将域模型一直返回到控制器,或者我们应该始终使用dto与服务层通信?

当然没关系!如果你有域实体,DTO和视图模型,那么包括数据库表,你在应用程序中重复的所有字段在4个地方。我曾参与过一些大型项目,其中领域实体和视图模型工作得很好。唯一的例外是,如果应用程序是分布式的,而服务层位于另一台服务器上,在这种情况下,由于序列化的原因,需要通过网络发送dto。

如果是,是否可以根据服务需求调整域模型?(坦白地说,我不这么认为,因为服务应该消费域拥有的东西。)

一般来说,我同意并说不,因为域模型通常是业务逻辑的反映,通常不是由该逻辑的消费者塑造的。

如果我们应该严格遵循dto,那么它们应该在服务层定义吗?(我想是的。)

如果你决定使用它们,我同意并说是的,服务层是一个完美的地方,因为它在一天结束时返回dto。

好运!

我来晚了,但这是一个如此常见而重要的问题,我觉得有必要做出回应。

你所说的“服务”是指Evan在蓝色的书中描述的“应用层”吗?我假设你这样做,在这种情况下,答案是他们应该返回dto。我建议阅读蓝皮书的第4章,标题为“隔离领域”。

在这一章中,Evans阐述了以下关于层次的内容:

把一个复杂的程序划分成几层。在每一层中开发一个内聚的设计,它只依赖于下面的层。

这是有充分理由的。如果你使用部分顺序作为软件复杂性的度量的概念,那么让一个层依赖于它上面的层会增加复杂性,从而降低可维护性。

将此应用于您的问题,dto实际上是用户界面/表示层所关注的适配器。请记住,远程/跨进程通信正是条例的目的(值得注意的是,在那篇文章中,Fowler也反对dto作为服务层的一部分,尽管他不一定是在谈论DDD语言)。

如果应用程序层依赖于这些dto,那么它就依赖于它上面的一层,复杂性就会增加。我可以保证这会增加维护软件的难度。

例如,如果您的系统与其他几个系统或客户端类型进行接口,每个系统或客户端类型都需要自己的DTO,该怎么办?如何知道应用程序服务的方法应该返回哪个DTO ?如果您所选择的语言不允许基于返回类型重载方法(在本例中是服务方法),您将如何解决这个问题呢?而且,即使您找到了一种方法,为什么要违背应用层来支持表示层关注点呢?

在实践中,这是朝着意大利面条式架构的方向迈进的一步。我亲身经历过这种权力下放及其结果。

在我目前工作的地方,应用程序层中的服务返回域对象。我们不认为这是一个问题,因为界面(即UI/表示)层依赖于域层,这是下面它。此外,这种依赖被最小化为“仅引用”类型的依赖,因为:

a)接口层只能将这些域对象作为调用应用层获得的只读返回值访问

b)应用层服务上的方法只接受该层定义的“原始”输入(数据值)或对象参数(必要时减少参数计数)。具体来说,应用程序服务从来没有接受域对象作为输入。

接口层使用在接口层本身定义的映射技术从域对象映射到dto。这再次使dto集中于由接口层控制的适配器。

根据我的经验,除非您使用的是OO UI模式(如裸对象),否则将域对象暴露给UI是一个坏主意。这是因为随着应用程序的增长,来自UI的需求会发生变化,并迫使您的对象适应这些变化。你最终服务于两个主人:UI和DOMAIN,这是一个非常痛苦的经历。相信我,你不会想去的。UI模型具有与用户通信的功能,DOMAIN模型用于保存业务规则,持久性模型用于有效地存储数据。它们都满足应用程序的不同需求。我正在写一篇关于这个的博客文章,当它完成时会添加它。

如果您返回域模型的一部分,它将成为契约的一部分。合同是很难改变的,因为你上下文之外的事情都依赖于它。因此,您将使部分域模型难以更改。

领域模型的一个非常重要的方面是它很容易更改。这使我们能够灵活地适应领域不断变化的需求。

虽然姗姗来迟,但我面对的是完全相同类型的体系结构,我倾向于“仅来自服务的dto”。这主要是因为我决定只使用域对象/聚合来维护对象内的有效性,因此只在更新、创建或删除时使用。在查询数据时,我们只使用EF作为存储库,并将结果映射到dto。这使得我们可以自由地优化读查询,而不是将它们适应业务对象,通常使用数据库函数,因为它们很快。

每个服务方法都定义了自己的契约,因此随着时间的推移更容易维护。我希望。