普通的CLR对象 vs 数据传输对象

436

POCO = 简单的旧CLR(或更好:类)对象

DTO = 数据传输对象

在这篇文章中存在差异,但实际上我阅读的大多数博客都将POCO描述为DTO定义的方式:DTO是用于在应用程序的层之间移动数据的简单数据容器。

POCO和DTO是相同的吗?


5
“POCO = Plain Old CLR(或更好的:Class)Object”,因此在VB.NET中,这种类型的对象也将是POCO,而不是POVO。 - J. Polfer
10个回答

597
一个POCO遵循OOP的规则。它应该(但不一定)拥有状态和行为。POCO源自于POJO,由Martin Fowler创造 [这里有个趣闻]。他使用POJO这个术语作为一种方式,使其更具吸引力来拒绝基于框架的EJB实现。在.Net中应该在相同的上下文中使用POCO。不要让框架决定您对象的设计。
DTO的唯一目的是传输状态,不应该有任何行为。请参阅Martin Fowler的DTO解释,了解此模式的用法示例。
这是区别所在:POCO描述了一种编程方法(好老式的面向对象编程),而DTO是一种模式,用于使用对象“传输数据”。
虽然您可以像处理DTO一样处理POCO,但这样做会产生贫血领域模型的风险。此外,结构不匹配,因为DTO应该被设计为传输数据,而不是代表业务领域的真实结构。结果是DTO往往比实际领域更扁平化。
在任何合理复杂的领域中,您几乎总是最好创建单独的领域POCO并将它们转换为DTO。DDD(领域驱动设计)定义了反腐层(另一个链接在这里,但最好的方法是购买该书),这是一个很好的结构,使分离清晰明了。

41
@Beatles1692,所述方法是序列化代码。说“没有行为”可能太笼统了。可以这么说:“没有业务逻辑”。序列化代码、低级对象处理(如哈希码、相等性和tostring)应该是可以接受的。 - Michael Meadows
不要称它们为DTO。它们被称为模型(Models)。在这些上面使用DTO是愚蠢的术语。 - PositiveGuy
在MVC中,模型通常包含业务逻辑,因此术语“DTO”不太可能被解释为不同的含义。 - KCD
2
@PositiveGuy 模型与DTO的目的不同。DTO应用于在不同领域之间传输数据(无论它们是否在相同的运行时中都是无关紧要的)。模型“代表”领域的某个方面,例如屏幕、服务或数据源。模型包括状态和行为,这些状态和行为代表了它们所建模的内容。 - Michael Meadows
4
请注意,贫血的领域模型并不一定是不好的,特别是如果你的应用程序大多数是CRUD操作。建议将简单性放在Martin Fowler之上。 - Mariusz Jamro
显示剩余3条评论

55

我已经在我的博客文章中表明了我的立场,因此为我做出贡献可能是多余的,但该文章的最后一段总结了一切:

因此,总之,学会喜欢POCO,并确保您不会传播任何关于它与DTO相同的错误信息。 DTO是用于在应用程序的各个层之间移动数据的简单数据容器。 POCO是完整的业务对象,其唯一要求是持久性无关(没有获取或保存方法)。 最后,如果您还没有查看Jimmy Nilsson的书,请从当地的大学图书馆中获取。 它有C#示例,是一本很棒的读物。

顺便说一句,Patrick,我把POCO作为生活方式文章阅读了一遍,我完全同意,那是一篇很棒的文章。 实际上,它是我推荐的Jimmy Nilsson书中的一节。 我不知道它可以在线获得。 他的书确实是我发现的有关POCO / DTO / Repository和其他DDD开发实践的最佳信息来源。


5
博客文章链接:http://rlacovara.blogspot.com/2009/03/what-is-difference-between-dto-and-poco.html - Jamie Ide

28

POCO是一个不依赖于外部框架的简单对象。它是“纯”的。

一个POCO是否有行为是无关紧要的。

DTO可能是POCO,领域对象也可能是POCO(通常富含行为)。

通常DTO更可能依赖于外部框架(例如属性)进行序列化,因为它们通常存在于系统边界处。

在典型的洋葱式架构(通常在广泛使用的DDD方法中使用),领域层位于中心位置,因此其对象在此阶段不应具有领域层以外的依赖关系。


17

我为这个主题写了一篇文章:DTO vs Value Object vs POCO

简而言之:

  • DTO != 值对象(Value Object)
  • DTO ⊂ 简单类型的实体(POCO)
  • 值对象(Value Object) ⊂ 简单类型的实体(POCO)

12

简而言之:

DTO描述了状态传输的模式。POCO除了没有特殊之外,几乎什么都不描述。这是面向对象编程中“对象”的另一种说法。它来自于POJO(Java),由Martin Fowler创造,他只是将其描述为“对象”的一个更高级的名称,因为“对象”并不太吸引人,人们对此避而远之。

详细解释...

好吧,为了解释这个问题,我从未想过需要用更高深的方式来解释,从你关于DTO的最初问题开始:

DTO是一种用于在关注点层之间传输状态的对象模式。它们可以具有行为(也就是可以是POCO),只要该行为不改变状态。例如,它可能有一个将自身序列化的方法。为了成为一个合适的DTO,它需要是一个简单的属性包;必须清楚地表明这个对象不是一个强大的模型,它没有暗示的语义含义,并且不强制执行任何形式的业务规则或不变量。它只是存在于数据传输中。

POCO是一个普通对象,但所谓“普通”是指它没有特殊要求或约定。它只是意味着它是一个没有隐含模式的CLR对象。一个通用的术语。我还听说它被扩展为描述它也不是为了与其他框架一起使用而制作的。因此,如果你的POCO在其属性上有大量的EF装饰,例如,那么我会认为它不是一个简单的POCO,而更多地属于DAO领域,我将其描述为DTO和额外的数据库关注点(例如映射等)的组合。POCOs像你在学校里学习创建的对象一样自由而无拘束。
以下是一些不同类型的对象模式的示例供比较:
  • 视图模型:用于为视图建模的数据。通常具有数据注释,以帮助特定视图的绑定和验证(即通常不是共享对象),或者在当今时代,特定的视图组件(例如React)。在MVVM中,它还充当控制器的角色。它不仅仅是一个数据传输对象(DTO);它不是传递状态,而是以对UI有用的方式呈现或更具体地说,形成该状态。
  • 值对象:用于表示值,应该是不可变的。
  • 聚合根:用于管理状态和不变性。不应允许引用内部实体,除非通过ID。
  • 处理器:用于响应事件/消息。
  • 属性:用作处理横切关注点的装饰。可能只允许在某些对象级别上使用(例如属性但不是类、方法但不是属性等)。
  • 服务:用于执行复杂任务。通常是某种形式的外观。
  • 控制器:用于控制请求和响应的流程。通常限制在特定的协议上或充当某种中介者;它有特定的责任。
  • 工厂:用于配置和/或组装复杂对象,以便在构造函数不足够时使用。还用于在运行时决定需要创建哪些对象。
  • 存储库/数据访问对象(DAO):用于访问数据。通常公开CRUD操作或表示数据库模式的对象;可能带有特定于实现的属性标记。实际上,其中一个模式DAO对象实际上是另一种类型的DTO...
  • API合同:可能带有序列化属性标记。通常需要具有公共的getter和setter,并且应该是轻量级的(不是过于复杂的图形);与序列化无关的方法不常见且不鼓励使用。
这些可以看作只是对象,但请注意,它们大多数都与某种模式相关联或具有隐含的限制。因此,你可以称之为“对象”,或者更具体地描述其意图并按其实际名称称呼。这也是为什么我们有设计模式的原因;用几个词来描述复杂的概念。DTO 是一种模式。聚合根是一种模式,视图模型是一种模式(例如 MVC 和 MVVM)。
POCO 不描述一种模式。它只是面向对象编程中对类/对象的不同称呼方式,可以是任何东西。将其视为一个抽象概念;它们可以指代任何东西。在我看来,这是一种单向关系,因为一旦一个对象达到只能清晰地服务于一个目的的程度,它就不再是 POCO。例如,一旦你使用装饰器标记你的类以使其与某个框架配合工作(即“仪器化”),它就不再是 POCO。因此,我认为存在一些逻辑关系,如下所示:
- DTO 是 POCO(直到被仪器化) - POCO 可能不是 DTO - 视图模型是 POCO(直到被仪器化) - POCO 可能不是视图模型
区分这两者的重点在于保持模式清晰和一致,以避免交叉关注并导致紧密耦合。例如,如果您有一个业务对象,它具有改变状态的方法,但也被装饰得非常复杂,包含了用于保存到SQL Server的EF装饰和JsonProperty,以便可以通过API端点发送回来。这个对象将不容易变更,并且很可能会被各种属性的变体所淹没(例如UserId、UserPk、UserKey、UserGuid等),其中一些被标记为不保存到数据库,其他则被标记为不序列化到JSON API端点。

所以如果你告诉我某个东西是 DTO,那么我很可能会确保它只用于传递状态。如果你告诉我某个东西是视图模型,那么我很可能会确保它不会保存到数据库,并且知道可以在其中放入一些“hacky”的东西,以确保数据可供 UI 使用。如果你告诉我某个东西是领域模型,那么我很可能会确保它不依赖于领域之外的任何东西,尤其是不依赖于任何技术实现细节(数据库、服务等),只能依赖于抽象。但如果你告诉我某个东西是 POCO,除了告诉我它不应该被检测和操作之外,实际上并没有提供太多信息。

示例

这是一个简单但准确的示例,应该很容易理解。

这个对象可能是 POCO,也可能是 DTO。它只是一个对象,没有什么特殊之处。看起来像是弱类型的属性包,但没有什么值得注意的地方。

public class CreateUserRequest
{
    public string Name { get; set; }

    public string Email { get; set; }
}

这已经不再是一个POCO了。
public class CreateUserRequest
{
    [JsonPropertyName(Name = "name")]
    public string Name { get; set; }

    [JsonPropertyName(Name = "email")]
    public string Email { get; set; }
}

为什么它不再是POCO了?因为它明显是一个与System.Text.Json序列化器配合使用的数据合同。现在更接近于DTO,但为特定框架进行了适配。
当你不做这些区分时就会出现下面这种情况。
[Table("Users")]
public class CreateUserRequest
{
    [Key]
    [JsonIgnore]
    public string Id { get; set; }

    [JsonPropertyName(Name = "name")]
    public string Name { get; set; }

    [JsonPropertyName(Name = "email")]
    public string Email { get; set; }

    public int LoginCount { get; set; }

    public void IncrementLogin() => LoginCount++;
}

现在这绝对不再是一个POCO了。它看起来像是某种DTO,但它的目的被过载了。它是一个API合同还是一个DAO?它似乎既可以作为JSON合同,又可以与数据库ORM一起使用。为了防止它将数据库PK泄露到REST API之外,需要进行额外的仪器化。它还有一个改变状态的方法,就好像有人将其用作领域实体一样。甚至开发人员自己也不清楚LoginCount是要成为JSON合同还是数据库模式的一部分。
我经常看到开发人员这样做,他们认为通过重用类可以节省时间。他们认为这是DRY原则。我想你可以争论说它是DRY原则,但实际上,它是人为的、紧密耦合的,可能违反了其他5个设计原则,并且最终会在未来给你带来麻烦。
历史记录
从Fowler的解释中改写而来:在一个物体都很花哨(比如遵循特定模式、有仪器等)的世界里,这种情况不知怎么地鼓励人们避免使用非花哨的物体来捕捉业务逻辑。所以他们给它起了一个花哨的名字POJO。如果你想要一个例子,他提到的是“实体Bean”,这是那些具有非常特定约定和要求的对象之一,等等。如果你不知道那是什么---> Java Beans
相比之下,POJO/POCO只是你在学校里学会如何创建的普通对象。

“在一个物体都很花哨的世界里…” 这让人想起了 GitHub 的喝酒游戏:打开 GitHub,输入一个名词。如果有相应的软件包存在,你就喝一口。如果没有,其他人就要喝。 - Tim

9
我认为DTO可以是POCO。DTO更多地涉及对象的使用,而POCO更多地涉及对象的风格(与架构概念解耦)。
一个POCO与DTO不同的例子是当你在谈论领域模型/业务逻辑模型内的POCO时,这是问题领域的一个良好的OO表现方式。你可以在整个应用程序中使用POCO,但这可能会有一些不良的副作用,例如知识泄漏。DTO通常从服务层使用,该服务层与UI通信,DTO是数据的平面表示形式,仅用于向UI提供数据,并将更改传递回服务层。服务层负责将DTO双向映射到POCO领域对象。
更新:马丁·福勒said这种方法是一条困难的路,只有在领域层和用户界面之间存在显著的不匹配时才应采取。

2
@David Landman,您提供的链接是关于本地DTO模式的,这种模式是在系统边界内使用DTO来传输状态。在这些情况下,您应该非常小心,因为在您的系统内部,您应该已经有一个明确定义的域可以共享。当跨越系统边界传输状态时,很难避免使用DTO,并且在所有情况下都非常适用。 - Michael Meadows
@Michal Meadows,是的,这个链接确实讨论了一组不同的问题。但是我认为在跨系统边界传输状态的情况下,您应该使用一个转换服务将一个上下文的POCO映射到另一个上下文的POCO。或者您是在谈论系统级别的边界吗? - Davy Landman

1
DTO最常见的用途是从Web服务中返回数据。在这种情况下,POCO和DTO是等价的。当从Web服务返回POCO时,其中的任何行为都将被移除,因此它是否具有行为并不重要。

5
我认为你的答案有点误导。对于网络服务而言,代理是根据对象公开状态生成的。这意味着会创建一个 DTO,它与 POCO 分离,只是恰巧具有与 POCO 相同的公共状态。这可能看起来微不足道,但很重要。原因是即使代理与原始对象完全相同,它实际上并不是从同一类构造的。 - Michael Meadows
@John Saunders,如果我没有表达清楚,对不起。我不是在质疑你的观点是否正确,而是在质疑它的语义。** 关于你的观点:DTO旨在保护您的POCO免受您所描述的问题。为了实现这一点,必须将DTO映射到POCO。在简单到中等复杂的解决方案中,这可能会过度,因此您是正确的。 - Michael Meadows
5
语义上:Web服务使用WSDL公开对象状态包。这些对象状态包生成代理。代理不能包含行为。如果使用Web服务,您的对象与公开域对象之间唯一的关系是它们具有相同的公共状态,该状态是基于检查创建的。 - Michael Meadows
7
@John,我觉得你有些过度反应了。我的意思是你是正确的,但措辞上有误导性。“在这种情况下,POCO和DTO是等价的。”从语义上讲,这不是真实的。POCO可以用作DTO,反之亦然,但这并不意味着它们是等价的......就像汽车和皮卡一样,它们都可以用于开车去杂货店,但它们没有等同之处,即使在购物的背景下,很难找到人告诉你洞察力等同于F350。 - Michael Meadows
4
这个回答非常错误,Web服务并不足够通用。最重要的是,已经有一个公认的事实:DTO不是POCO。DTO是一个数据容器,而POCO是具有属性的对象,并且与持久性无关(没有get或save方法)。 - Tom Stickel
显示剩余2条评论

1
DTO对象用于将数据从不同的源反序列化为对象。这些对象不是您的模型(POCO)对象。您需要将这些对象转换为您的模型(POCO)对象。转换主要是复制操作。如果是内部源,则可以直接从源中填充这些POCO对象,但如果是外部源,则不建议这样做。外部源具有使用架构的API描述。然后,更容易地将请求数据加载到DTO中,然后将其转换为您的POCO。是的,这是一个额外的步骤,但有原因。规则是在对象中加载来自源的数据。它可以是JSON、XML等任何格式。加载后,将该数据转换为您在模型中所需的内容。因此,大多数情况下,DTO是外部源的对象映像。有时甚至可以获取源提供程序的架构,然后可以更轻松地反序列化,XML就可以使用XSD进行这样的操作。

-1

这是一个普遍的规则:DTO==有害,是过度工程化软件的指示器。POCO==好。"企业级"模式已经毁掉了很多Java EE世界中人们的思维。请不要在.NET领域重蹈覆辙。


7
请您详细阐述一下?为了避免在合同中涉及实现和特定平台的细节,从 Web 服务返回数据时需要使用 DTO。 - John Saunders
1
是的,John DTO是为您所说的目的而设计的,并且工作得很好。但不幸的是,在单层Web应用程序中经常会在不需要时使用它们,并且几乎没有价值。 - Craig
9
我认为,@drscroogemcduck,你可能不喜欢DTO,因为它们被视为首选而不是最后选择,但它们本质上并不邪恶......与臭名昭著的单例或工厂模式一样。邪恶的是将框架强加给开发人员的架构师,迫使他们为每件事情制作DTO。就其所做的数据传输而言,DTO(如果谨慎地执行)是完美的选择。 - Michael Meadows
这并不是邪恶的。一切都取决于良好的设计。如果设计不好,你确实会过度工程化。如果网站中的所有内容都是可服务的,你会发现这一点。把所有东西放在 DomainModel 中,并在可能的情况下进行聚合。为每个东西创建对象是错误的。 - Herman Van Der Blom

-14

甚至不要称它们为DTO。它们被称为模型......就是这样。模型从不具有行为。我不知道是谁想出了这个愚蠢的DTO术语,但我想这一定是.NET的事情。在MVC中考虑视图模型,同样的东西,模型用于在服务器端或通过网络传输状态,它们都是模型。带有数据的属性。这些是您通过网络传递的模型。模型,模型,模型。就是这样。

我希望愚蠢的DTO术语能够从我们的词汇表中消失。


1
我不知道你从哪里得到这个想法,认为模型从不具有行为。如果没有对行为进行建模,如何对除CRUD之外的任何内容进行建模?即使在许多情况下,ViewModels也具有行为,特别是在MVVM应用程序中。DTO是一个有用的术语,因为它准确地描述了其目的:传输数据。 - Gerald
16
因为事实不正确且态度炫耀而被踩。 - joedotnot
胡说八道。模型应该是愚蠢的容器。没有DTO,这是微软编造的术语。你在域、服务和应用程序之间传输模型。就这样。DTO是一个不必要的浪费术语,只会更加混淆事情。模型,模型和更多模型,就是这样。模型可能有行为,也可能没有。视图模型不应该有行为。那种行为应该在BL中,而不是在模型类中。 - PositiveGuy
我同意DTO在功能上是模型。ViewModels具有行为,是MVVM中绑定的对象。然而,我编写了一个应用程序,其中我的Models更加智能(基本上是VMs,但我不想称它们为VMs),它们“接受”DTO对象。这使我可以在框架中拥有更多选项。因此,从CRUD(甚至EF)中,我将通过WCF服务传输对象并接收DTO对象,并封装它(添加OnProp Change等)。我的ViewModels执行进一步的封装,可能会接受两个(或列表)“Models”。严格的定义将是VMs。 - SQLMason
你可以在不同的领域、服务和应用程序之间传输模型。为什么您认为术语“模型”比“DTO”更适合描述这种行为? - caa
所以在所有的ORM中,那些所谓的“模型”都是错误的。明白了。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接