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
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 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
                            В чем вы видите противоречие?

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