Пользователь
0,0
рейтинг
7 октября 2015 в 06:49

Разработка → DTO vs POCO vs Value Object

В этой статье я бы хотел прояснить различия между 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
Vladimir @vkhorikov
карма
43,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

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

    вообще Entity подразумевают уникальный ключ. Если и он совпадает то это таки одна сущность. По крайней мере одна бизнес-сущность.
    • +3
      Суррогатные ключи часто не считают свойством сущности, а натуральных может и не быть.
      • +4
        Можно даже сказать, что суррогатный ключ в каком-то виде — протекающая абстракция.
        • +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, т.к. для этого нужно дополнительно инжектить репозиторий. С другой стороны известно, что инжектить нужно не очень много, а если пришлось инжектить много зависимостей — у вас неправильный дизайн.
  • +2
    На самом деле, POCO все же обычно отличают от DTO, потому что POCO это не просто там класс, а класс моделирующий домен. Он содержит в себе не только данные но и поведение. Это принципиальное отличие см например статью об анемичной доменной модели.
    • +3
      Это позднее наслоение. POCO — это действительно просто plain old C(#) class, в противовес тяжеловесному наследнику чего угодно.
      • +1
        Не class а Object. И это отсылка на ООП которое не привествует объекты без поведения.
        • 0
          Object, да, задумался. А вот по поводу отсылки не уверен я.
          • +3
            Я тут нагуглил вопрос на StackOverflow:
            смотреть ответы.

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

      Что-то не вижу как сказанное вами противоречит тому, что написано в статье
      • +1
        Тем что множество DTO не входит во множество POCO.
  • 0
    Value Object-ы могут содержать логику

    Мне всегда казалось, что Value Object — простые иммутабельные объекты типа дат или денег. Никакой логики в них быть не должно, заисключением, разве что, логики сравнения и определения равества. Они как бы строительные кирпичики для Entity, Business Objects, DTO и пр.
    У Вас в кругах Эйлера так и нарисовано Value Object ⊂ POCO. Какой же это Plain Object, если он с логикой? POCO (POJO, PODS) — это принцип организации данных, пассивные структуры. DTO — это роль, которую данные выполняют (Transfer).
    • +2
      А почему POCO (POJO) не может содержать логику?
      • 0
        Очевидно же, smilegs — сторонник анемичной модели :-).
      • 0
        Вопрос в том, что такое логика.
        Если рассматривать например даты, то не вижу проблем добавить методов работы с самими датами.
        Но если брать какой-нибудь объект типа «товар», неужели это нормально создавать класс в котором будут методы по добавлению товаров в БД, отправки их по сети, получения связанных сущностей типа всех покупателей? Не будет ли это перегружать класс, и создавать проблем с тестированием, распараллеливанием, поддержкой?
        • 0
          То, что вы описываете — это нарушение SRP, и не является специфичной особенностью value types (собственно, вы и пример-то приводите от сущности).
          • 0
            Извиняюсь за глупые вопросы. Возможно, автор статьи (@vkhorikov) подскажет.
            Но можно привести пример POCO с логикой, чтоб он не нарушал SRP, и не был VO?
            Будет ли единственным отличием POCO и VO иммутабельность последнего?
            Можно ли вообще обойтись без термина POCO, ограничившись DTO (объекты без логики) и VO (объекты с тривиальной логикой)?
            • +1
              Но можно привести пример POCO с логикой, чтоб он не нарушал SRP, и не был VO?

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

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

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

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

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

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

          А вот это пожалуйста. В JPA есть аннотация ManyToMany. При этом это не будет «логикой», это делает JPA-провайдер (который является частью инфраструктуры), а внутри — просто Set, Map или List.
    • 0
      Простите, но все три определения ввел в обиход Мартин Фаулер. И он в них вложил вполне определенный смысл. POCO — если переводить на русский «Старый добрый C# объект» объекты в объектно-ориентированном программировании содержат не только данные но и поведение.
      • 0
        Не знал, что Plain переводится как «старый» :)
        • 0
          Не поверите, как старый переводится Old. Plain Old C#(или CLR) Object.
          • 0
            Если переводить пословно получится что-то типа
            «Обыкновенный старый C# объект». Но, учитывая что вводилось это понятие как клевое название для обычных классических объектов, перевод «Старый добрый C# объект» тут вполне в тему и звучит более по-русски
            • 0
              Если вдаваться в трудности перевода, то перевод слова Plain как «Плоский», более отражает суть и поведение объекта — простой и без наследования.
              Плоский объект для меня — добрый объект :) Кому как удобнее :)
              • 0
                более отражает суть и поведение объекта — простой и без наследования.

                Так наследование-то там вполне может быть.
              • +1
                А автор не вкладывал сюда значение «плоский». Он вкладывал значение обычный (то есть не навязанный никаким фреймворком). При этом если доменная модель предполагает для конкретного объекта наследование — наследование будет, просто это наследование должно быть по причине такого домена, а не потому что наш фреймворк требует чтобы все сущности были отнаследованы от абстрактного класса Component.
          • 0
            не знаю, чем я думал :) хотел написать «добрый» :) типа Plain переводится как «добрый».
    • +2
      Мнение, которое вы высказали, и послужило причиной написания этой статьи.
      Value Object — иммутабельные объекты
      Это верно.
      Они как бы строительные кирпичики для Entity
      Это тоже верно.
      Никакой логики в них быть не должно
      Вот это уже неверно. Value Object-ы вполне себе могут содержать логику. Более того, Эванс и ко рекомендуют помещать доменную логику именно в Value Object-ы там где это возможно, т.к. работать с ними проще из-за их неизменяемости. Хороший пример Value Object-а — DateTime в .NET. Неизменямый тип данных с большим количеством логики внутри.
      POCO (POJO, PODS) — это принцип организации данных
      POCO — это в первую очередь принцип, который говорит о том, что при моделировании предметной области следует использовать настолько простые классы, насколько возможно. Это понятие никак не связано с DTO (кроме того, что DTO тоже не следует наследовать от тяжеловесных компонент).
  • 0
    На 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? Ну да, сравнивается и копируется по значению.
    • 0
      Как правило, Value Object-ы (так же как и Entities) не используются для передачи данных между приложениями. В целом, в простых случаях (как например в вашем) Value Object-ы действительно можно использовать как DTO, но опять же — это до тех пор, пока в них не скопилось достаточно логики.

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

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.