服务应该总是返回DTO,还是它们还可以返回域模型?

时间:2014-02-04 14:19:08

标签: asp.net-mvc entity-framework architecture domain-driven-design

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

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

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

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

文章Domain vs DTO vs ViewModel - How and When to use them?(以及其他一些文章)与我的问题非常相似,但它没有回答这个问题。文章Should I implement DTOs in repository pattern with EF?也类似,但它不涉及DDD。

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

一如既往,谢谢。

9 个答案:

答案 0 :(得分:121)

  

当域模型离开业务层(服务层)

时,感觉不对

让你觉得你正在把胆量拉出来吗?根据Martin Fowler的说法:服务层定义了应用程序的boundery,它封装了域。换句话说,它保护域名。

  

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

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

  

如果我们应该严格遵守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,演示文稿和域紧密耦合。 (这对小型项目来说没问题。)
  • 接口/ API稳定性
  • 可以通过返回仅包含绝对必需的属性的DTO来提供表示层的优化。使用linq-projection,您无需拉动整个实体。
  • 要降低开发成本,请使用代码生成工具

答案 1 :(得分:8)

根据我的经验,你应该做实事。 "最好的设计是最简单的设计,并且#34; - 爱因斯坦这就是心灵......

  

如果我们严格使用视图模型,可以将域模型一直返回到控制器,还是应该总是使用DTO与服务层进行通信?

绝对没问题!如果您有域实体,DTO和视图模型,那么包括数据库表,您可以在4个位置重复应用程序中的所有字段。我曾经在Domain Entities和View Models工作得很好的大型项目上工作过。唯一的例外是,如果应用程序是分布式的,并且服务层驻留在另一台服务器上,在这种情况下,由于序列化原因,DTO需要通过线路发送。

  

如果是这样,是否可以根据需要的服务调整域模型? (坦率地说,我不这么认为,因为服务应该消耗域名所具有的。)

通常我同意并拒绝,因为域模型通常是业务逻辑的反映,并且通常不会被该逻辑的消费者所塑造。

  

如果我们应该严格遵守DTO,是否应该在服务层中定义? (我想是的。)

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

祝你好运!

答案 2 :(得分:7)

当您决定采用DDD方法时,您的应用程序似乎很大且非常复杂。 不要在服务层中返回您的poco实体或所谓的域实体和值对象。如果您想这样做,请删除您的服务层,因为您不再需要它了!视图模型或数据传输对象应位于服务层中,因为它们应映射到域模型成员,反之亦然。 那么为什么你需要有DTO?在具有大量场景的复杂应用程序中,您应该区分域和您的表示视图的关注点,域模型可以分为几个DTO,并且几个域模型可以折叠成DTO。因此,最好在分层架构中创建DTO,即使它与您的模型相同。

我们是否应始终使用DTO与服务层进行通信? 是的,您必须通过服务层返回DTO,因为您在服务层与域模型成员对话时将其映射到DTO并返回到MVC控制器,反之亦然。

根据需要的服务调整域模型是否可以? 服务只涉及存储库和域方法以及域服务,您应该根据需要解决域中的业务,而不是告诉域需要什么的服务任务。

如果我们应该严格遵守DTO,是否应该在服务层中定义?是的,尝试让DTO或ViewModel稍后服务,因为它们应该映射到服务层中的域成员,它是将DTO置于应用程序的控制器中(尝试在服务层中使用Request Response模式)并不是一个好主意,欢呼!

答案 3 :(得分:3)

  

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

由于Domain Model为整个应用程序提供术语(Ubiquitous Language),因此最好广泛使用域模型。

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

  

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

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

我建议您尽可能返回域名实体。如果需要返回其他信息,则可以返回包含多个域实体的DTO。

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

  

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

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

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

答案 4 :(得分:2)

我建议分析这两个问题:

  1. 您的上层(即视图和视图模型/控制器)是否以与域图层公开的方式不同的方式使用数据?如果有很多映射正在进行甚至涉及逻辑,我建议你重新审视你的设计:它应该更接近数据的实际使用方式。

  2. 你深深改变上层的可能性有多大? (例如,为WPF交换ASP.NET)。如果这是非常不同的,并且您的架构不是很复杂,那么您最好尽可能多地暴露域实体。

  3. 我担心这是一个相当广泛的主题,它确实可以归结为您的系统及其要求的复杂程度。

答案 5 :(得分:2)

我参加这个聚会的时间已经很晚了,但这是一个我认为不得不作出回应的普遍而重要的问题。

通过"服务"你的意思是"应用层" Evan在blue book中描述的?我假设你这样做,在这种情况下答案是他们应该返回DTO。我建议阅读蓝皮书中的第4章,标题为"隔离域"。

在那一章中,埃文斯说了以下关于层的内容:

  

将复杂程序划分为多个层。在每个层中开发一个具有凝聚力的设计,并且仅取决于下面的层。

这是有充分理由的。如果你使用partial order as a measure of software complexity的概念,那么一个层依赖于上面的层会增加复杂性,从而降低可维护性。

将此问题应用于您的问题,DTO实际上是用户界面/表示层关注的适配器。请记住,远程/跨进程通信正好是purpose of a DTO(值得注意的是,在该帖子中,福勒也反对将DTO作为服务层的一部分,尽管他并不一定会谈论DDD式语言)。

如果您的应用程序层依赖于这些DTO,则它依赖于自身上方的层,并且您的复杂性会增加。我可以保证这会增加维护软件的难度。

例如,如果您的系统与其他几个系统或客户端类型连接,每个系统或客户端类型都需要自己的DTO,该怎么办?您如何知道应用程序服务的哪种方法应该返回?如果您选择的语言不允许基于返回类型重载方法(在这种情况下为服务方法),您甚至会如何解决该问题?即使您想出办法,为什么违反您的应用层以支持表示层关注?

实际上,这是一条以意大利面建筑结束的道路。我已经看到了这种权力下放及其结果。

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

a)接口层只能访问这些Domain对象作为通过调用Application层获得的只读返回值

b)应用层中的服务方法只接受输入" raw"在该层中定义的输入(数据值)或对象参数(以便在必要时减少参数计数)。具体来说,应用程序服务从不接受域对象作为输入。

接口层使用接口层本身定义的映射技术从域对象映射到DTO。同样,这使DTO专注于由接口层控制的适配器。

答案 6 :(得分:2)

如果您退还部分域模型,则该部分将成为合同的一部分。合同很难更改,因为上下文之外的事情都取决于它。因此,您将难以更改域模型的一部分。

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

答案 7 :(得分:1)

根据我的经验,除非您使用OO UI模式(例如裸对象),否则将域对象暴露给UI是一个坏主意。这是因为随着应用程序的增长,UI的需求会发生变化,并迫使您的对象适应这些变化。您最终将获得2个大师:UI和DOMAIN,这是非常痛苦的体验。相信我,你不想在那里。 UI模型具有与用户进行通信的功能,DOMAIN模型具有保存业务规则的功能,而持久性模型则具有有效存储数据的功能。它们都满足应用程序的不同需求。我正在撰写有关此内容的博客文章,请在完成后添加它。

答案 8 :(得分:1)

该晚了,但是我正面临着完全相同的架构类型,并且我倾向于“仅服务中的DTO”。这主要是因为我决定仅使用域对象/聚合来保持对象内的有效性,因此只能在更新,创建或删除时使用。查询数据时,我们仅使用EF作为存储库,并将结果映射到DTO。这使我们可以自由地优化读取查询,而不必使其适应业务对象,经常使用数据库函数,因为它们的速度很快。

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