В этой статье я бы хотел прояснить различия между DTO (Data Transfer Object), Value Object и POCO (Plain Old CLR Object), также известным как POJO в среде Java.

Определения DTO, POCO и Value Object


Вначале небольшая ремарка по поводу Value Object. В C# существует похожая концепция, называемая Value Type. Это всего лишь деталь имплементации того, как объекты хранятся в памяти и мы не будем касаться этого. Value Object, о котором пойдет речь, — потяние из среды DDD (Domain-Driven Design).

Ок, давайте начнем. Вы возможно заметили, что такие понятия как DTO, Value Object и POCO часто используются как синонимы. Но действительно ли они означают одно и то же?

DTO — это класс, содержащий данные без какой-либо логики для работы с ними. DTO обычно используются для передачи данных между различными приложениями, либо между слоями внутри одного приложения. Их можно рассматривать как хранилище информации, единственная цель которого — передать эту информацию получателю.

С другой стороны, Value Object — это полноценный член вашей доменной модели. Он подчиняется тем же правилам, что и сущности (Entities). Единственное отличие между Value Object и Entity в том, что у Value Object-а нет собственной идентичности. Это означает, что два Value Object-а с одинаковыми свойствами могут считаться идентичными, в то время как две сущности отличаются друг от друга даже в случае если их свойства полностью совпадают.

Value Object-ы могут содержать логику и обычно они не используются для передачи информации между приложениями.

POCO (Plain Old CLR Object) — термин, созданный как аналогия для POJO. POJO не может использоваться в .NET из-за того, что буква “J” в нем означает “Java”. POCO имеет ту же семантику, что и POJO.

POJO был представлен Мартином Фаулером в качестве альтернативы для JavaBeans и других «тяжелых» enterprise-конструкций, которые были популярны в ранних 2000-х.

Основной целью POJO было показать, что домен приложения может быть успешно смоделирован без использования JavaBeans. Более того, JavaBeans вообще не должны быть использованы для этой цели.

В среде .NET нет прямой аналогии для JavaBeans, т.к. Microsoft никогда не представляла ничего похожего, но мы можем придумать некоторую параллель.

Вы можете думать о классе Component из System.ComponentModel как о понятии, противоположном POCO. В .NET существует множество классов, наследующих от Component, например DBCommand из System.Data или EventLog из System.Diagnostics.

Наследовать доменные классы от Component в большинстве случаев не имеет никакого смысла, т.к. это решение привносит в модель ненужную сложность. Использование тяжеловесных классов из .NET Framework для подобных целей противоречит принципу YAGNI.

Другой хороший пример анти-POCO подхода — Entity Framework до версии 4.0. Каждый класс, сгенерированный EF, наследовал от EntityObject, что привносило в домен логику, специфичную для EF. Начиная с версии 4, Entity Framework добавил возможность работать с POCO моделью — возможность использовать классы, которые не наследуются от EntityObject.

Таким образом, понятие POCO означает использование настолько простых классов насколько возможно для моделирования предметной области. Это понятие помогает придерживаться принципов YAGNI, KISS и остальных best practices. POCO классы могут содержать логику.

Коррелация между понятиями


Есть ли связи между этими тремя понятиями? В первую очередь, DTO и Value Object отражают разные концепции и не могут использоваться взаимозаменяемо. С другой стороны, POCO — это надмножество для DTO и Value Object:

image

Другими словами, Value Object и DTO не наследуют никаким сторонним компонентам и таким образом являются POCO. В то же время, POCO — это более широкое понятие: это может быть Value Object, Entity, DTO или любой другой класс в том случае если он не наследует компонентам, не относящимся напрямую к решаемой вами проблеме.

Вот свойства каждого из них:

image

Заметьте, что POCO-класс может и иметь, и не иметь собственной идентичности, т.к. он может быть как Value Object, так и Entity. Также, POCO может содержать, а может и не содержать логику внутри себя. Это зависит от того, является ли POCO DTO.

Заключение


Вышесказанное в статье можно суммировать следующим образом:

  • DTO != Value Object
  • DTO ? POCO
  • Value Object ? POCO

Английская версия статьи: DTO vs Value Object vs POCO

Комментарии (32)


  1. caballero
    07.10.2015 09:31
    +2

    в то время как две сущности отличаются друг от друга даже в случае если их свойства полностью совпадают.

    вообще Entity подразумевают уникальный ключ. Если и он совпадает то это таки одна сущность. По крайней мере одна бизнес-сущность.


    1. lair
      07.10.2015 10:44
      +3

      Суррогатные ключи часто не считают свойством сущности, а натуральных может и не быть.


      1. Nagg
        07.10.2015 11:31
        +4

        Можно даже сказать, что суррогатный ключ в каком-то виде — протекающая абстракция.


        1. konsoletyper
          07.10.2015 16:11
          +2

          В каком-то смысле да. Обычно, в Java я делаю следующее: для поля id не делаю публичных getter/setter, добавляю коммент, что это костыль для JPA/Hibernate. А делаю я так вот из каких соображений. em.persist говорит, что entity должен быть persistent. При этом реальная синхронизация с БД может произойти несколько позже. А значит, мы получим поведение: id, который был null внезапно становится не null. WTF? Более того, для id нельзя делать сеттер, иначе какой он тогда id? Тем более WTF.

          Согласно принципу минимизации WFT я делаю суррогатный ключ свойством репозитория. Т.е. у репозитория имеются методы getId(EntityType), findById(int id). Первый тащит id с помощью PersistentUnitUtil. По-хорошему, должна была быть аннотация вроде SurrogateId, применяемая к классу.

          В принципе оно нормально работает, вот только не очень удобно во всяких remote facade таскать этот id в presentation, т.к. для этого нужно дополнительно инжектить репозиторий. С другой стороны известно, что инжектить нужно не очень много, а если пришлось инжектить много зависимостей — у вас неправильный дизайн.


  1. mird
    07.10.2015 13:30
    +2

    На самом деле, POCO все же обычно отличают от DTO, потому что POCO это не просто там класс, а класс моделирующий домен. Он содержит в себе не только данные но и поведение. Это принципиальное отличие см например статью об анемичной доменной модели.


    1. lair
      07.10.2015 13:37
      +3

      Это позднее наслоение. POCO — это действительно просто plain old C(#) class, в противовес тяжеловесному наследнику чего угодно.


      1. mird
        07.10.2015 14:02
        +1

        Не class а Object. И это отсылка на ООП которое не привествует объекты без поведения.


        1. lair
          07.10.2015 14:04

          Object, да, задумался. А вот по поводу отсылки не уверен я.


          1. mird
            07.10.2015 14:06
            +3

            Я тут нагуглил вопрос на StackOverflow:
            смотреть ответы.

            Там есть принятый сообществом ответ и непринятый сообществом ответ, который ссылается на англоязычную версию этой статьи.


            1. lair
              07.10.2015 14:10
              +1

              Ну кстати да, принятый ответ очень хорош.


    1. vkhorikov
      07.10.2015 14:12

      На самом деле

      Что-то не вижу как сказанное вами противоречит тому, что написано в статье


      1. mird
        07.10.2015 14:50
        +1

        Тем что множество DTO не входит во множество POCO.


  1. smilegs
    07.10.2015 19:56

    Value Object-ы могут содержать логику

    Мне всегда казалось, что Value Object — простые иммутабельные объекты типа дат или денег. Никакой логики в них быть не должно, заисключением, разве что, логики сравнения и определения равества. Они как бы строительные кирпичики для Entity, Business Objects, DTO и пр.
    У Вас в кругах Эйлера так и нарисовано Value Object ? POCO. Какой же это Plain Object, если он с логикой? POCO (POJO, PODS) — это принцип организации данных, пассивные структуры. DTO — это роль, которую данные выполняют (Transfer).


    1. lair
      07.10.2015 19:59
      +2

      А почему POCO (POJO) не может содержать логику?


      1. Nagg
        07.10.2015 20:04

        Очевидно же, smilegs — сторонник анемичной модели :-).


      1. smilegs
        08.10.2015 10:17

        Вопрос в том, что такое логика.
        Если рассматривать например даты, то не вижу проблем добавить методов работы с самими датами.
        Но если брать какой-нибудь объект типа «товар», неужели это нормально создавать класс в котором будут методы по добавлению товаров в БД, отправки их по сети, получения связанных сущностей типа всех покупателей? Не будет ли это перегружать класс, и создавать проблем с тестированием, распараллеливанием, поддержкой?


        1. lair
          08.10.2015 10:19

          То, что вы описываете — это нарушение SRP, и не является специфичной особенностью value types (собственно, вы и пример-то приводите от сущности).


          1. smilegs
            08.10.2015 10:56

            Извиняюсь за глупые вопросы. Возможно, автор статьи (@vkhorikov) подскажет.
            Но можно привести пример POCO с логикой, чтоб он не нарушал SRP, и не был VO?
            Будет ли единственным отличием POCO и VO иммутабельность последнего?
            Можно ли вообще обойтись без термина POCO, ограничившись DTO (объекты без логики) и VO (объекты с тривиальной логикой)?


            1. lair
              08.10.2015 11:20
              +1

              Но можно привести пример POCO с логикой, чтоб он не нарушал SRP, и не был VO?

              Любая бизнес-сущность.

              Вы смешиваете объекты, находящиеся в разных областях. POCO — это термин из области реализации, означающий, что объект не наследует от какого-нибудь Component или Entity, навязанного инфраструктурой. Value Object и Entity — это термины из области моделирования (например, DDD), и они означают разделение объектов в модели на имеющие свою идентичность (сущности), и не имеющие таковой (объекты-значения).


              1. smilegs
                08.10.2015 11:29

                Спасибо.
                Коментарий, проясняет суть вопроса лучше чем статья :)
                Сказано не в обиду автору, автор тоже млодец.


        1. konsoletyper
          08.10.2015 10:50

          методы по добавлению товаров в БД

          Нет, добавление товаров в БД — это не часть бизнес-логики, а часть инфраструктуры. См. паттерн repository

          отправки их по сети

          Если отправка осуществляется путём выставления какого-нибудь REST API, то это часть presentation layer, который выше слоем над domain model. Если задача обратная — надо постучать в удалённый сервис, например, по SOAP, то пишется интерфейс ProductConsumer, реализация которого может его отправлять куда и как угодно (и скорее всего, даже не пишется вручную, а генерится каким-нибудь CXF или Jersey). При этом сам класс Product вполне может содержать методы, которые решают, когда товар отправлять и нужно ли его вообще отправлять, что уже является частью бизнес-логики (потому что такие вещи формулируются заказчиком).

          получения связанных сущностей типа всех покупателей

          А вот это пожалуйста. В JPA есть аннотация ManyToMany. При этом это не будет «логикой», это делает JPA-провайдер (который является частью инфраструктуры), а внутри — просто Set, Map или List.


    1. mird
      07.10.2015 20:35

      Простите, но все три определения ввел в обиход Мартин Фаулер. И он в них вложил вполне определенный смысл. POCO — если переводить на русский «Старый добрый C# объект» объекты в объектно-ориентированном программировании содержат не только данные но и поведение.


      1. smilegs
        08.10.2015 10:24

        Не знал, что Plain переводится как «старый» :)


        1. mird
          08.10.2015 10:55

          Не поверите, как старый переводится Old. Plain Old C#(или CLR) Object.


          1. mird
            08.10.2015 11:00

            Если переводить пословно получится что-то типа
            «Обыкновенный старый C# объект». Но, учитывая что вводилось это понятие как клевое название для обычных классических объектов, перевод «Старый добрый C# объект» тут вполне в тему и звучит более по-русски


            1. smilegs
              08.10.2015 11:15

              Если вдаваться в трудности перевода, то перевод слова Plain как «Плоский», более отражает суть и поведение объекта — простой и без наследования.
              Плоский объект для меня — добрый объект :) Кому как удобнее :)


              1. lair
                08.10.2015 11:20

                более отражает суть и поведение объекта — простой и без наследования.

                Так наследование-то там вполне может быть.


              1. mird
                08.10.2015 14:18
                +1

                А автор не вкладывал сюда значение «плоский». Он вкладывал значение обычный (то есть не навязанный никаким фреймворком). При этом если доменная модель предполагает для конкретного объекта наследование — наследование будет, просто это наследование должно быть по причине такого домена, а не потому что наш фреймворк требует чтобы все сущности были отнаследованы от абстрактного класса Component.


          1. smilegs
            08.10.2015 11:02

            не знаю, чем я думал :) хотел написать «добрый» :) типа Plain переводится как «добрый».


    1. vkhorikov
      07.10.2015 20:36
      +2

      Мнение, которое вы высказали, и послужило причиной написания этой статьи.

      Value Object — иммутабельные объекты
      Это верно.
      Они как бы строительные кирпичики для Entity
      Это тоже верно.
      Никакой логики в них быть не должно
      Вот это уже неверно. Value Object-ы вполне себе могут содержать логику. Более того, Эванс и ко рекомендуют помещать доменную логику именно в Value Object-ы там где это возможно, т.к. работать с ними проще из-за их неизменяемости. Хороший пример Value Object-а — DateTime в .NET. Неизменямый тип данных с большим количеством логики внутри.
      POCO (POJO, PODS) — это принцип организации данных
      POCO — это в первую очередь принцип, который говорит о том, что при моделировании предметной области следует использовать настолько простые классы, насколько возможно. Это понятие никак не связано с DTO (кроме того, что DTO тоже не следует наследовать от тяжеловесных компонент).


  1. Athari
    08.10.2015 00:47

    На StackOverflow закинули ссылку на вашу статью, родилось обсуждение Наглядный пример различия DTO, POCO (POJO) и Value Object.

    В целом я с вашей статьёй согласен, но не пойму, почему на диаграмме Венна DTO и VO у вас не пересекаются. Скажем, возьмём банальную точку:

    struct Point {
        public int X;
        public int Y;
    }

    (По вкусу заменить r/w поля на r/w свойства, r/o поля, r/o свойства.)

    POCO? Ну да, куда уж плейн-олдее, одни поля. DTO? Ну да, можно передавать туда-сюда, сериализовать. VO? Ну да, сравнивается и копируется по значению.


    1. vkhorikov
      08.10.2015 01:06

      Как правило, Value Object-ы (так же как и Entities) не используются для передачи данных между приложениями. В целом, в простых случаях (как например в вашем) Value Object-ы действительно можно использовать как DTO, но опять же — это до тех пор, пока в них не скопилось достаточно логики.

      Хорошей практикой считается изолировать домен приложения. На практике это означает, что доменные классы (Entities and Value Objects) не должны знать о том, как они сериализуются. В более-менее сложных случаях этого трудно достичь если сериализовывать их напрямую.