Определения 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:
Другими словами, Value Object и DTO не наследуют никаким сторонним компонентам и таким образом являются POCO. В то же время, POCO — это более широкое понятие: это может быть Value Object, Entity, DTO или любой другой класс в том случае если он не наследует компонентам, не относящимся напрямую к решаемой вами проблеме.
Вот свойства каждого из них:
Заметьте, что POCO-класс может и иметь, и не иметь собственной идентичности, т.к. он может быть как Value Object, так и Entity. Также, POCO может содержать, а может и не содержать логику внутри себя. Это зависит от того, является ли POCO DTO.
Заключение
Вышесказанное в статье можно суммировать следующим образом:
- DTO != Value Object
- DTO ? POCO
- Value Object ? POCO
Английская версия статьи: DTO vs Value Object vs POCO
Комментарии (32)
mird
07.10.2015 13:30+2На самом деле, POCO все же обычно отличают от DTO, потому что POCO это не просто там класс, а класс моделирующий домен. Он содержит в себе не только данные но и поведение. Это принципиальное отличие см например статью об анемичной доменной модели.
lair
07.10.2015 13:37+3Это позднее наслоение. POCO — это действительно просто plain old C(#) class, в противовес тяжеловесному наследнику чего угодно.
mird
07.10.2015 14:02+1Не class а Object. И это отсылка на ООП которое не привествует объекты без поведения.
lair
07.10.2015 14:04Object, да, задумался. А вот по поводу отсылки не уверен я.
mird
07.10.2015 14:06+3Я тут нагуглил вопрос на StackOverflow:
смотреть ответы.
Там есть принятый сообществом ответ и непринятый сообществом ответ, который ссылается на англоязычную версию этой статьи.
smilegs
07.10.2015 19:56Value Object-ы могут содержать логику
Мне всегда казалось, что Value Object — простые иммутабельные объекты типа дат или денег. Никакой логики в них быть не должно, заисключением, разве что, логики сравнения и определения равества. Они как бы строительные кирпичики для Entity, Business Objects, DTO и пр.
У Вас в кругах Эйлера так и нарисовано Value Object ? POCO. Какой же это Plain Object, если он с логикой? POCO (POJO, PODS) — это принцип организации данных, пассивные структуры. DTO — это роль, которую данные выполняют (Transfer).lair
07.10.2015 19:59+2А почему POCO (POJO) не может содержать логику?
smilegs
08.10.2015 10:17Вопрос в том, что такое логика.
Если рассматривать например даты, то не вижу проблем добавить методов работы с самими датами.
Но если брать какой-нибудь объект типа «товар», неужели это нормально создавать класс в котором будут методы по добавлению товаров в БД, отправки их по сети, получения связанных сущностей типа всех покупателей? Не будет ли это перегружать класс, и создавать проблем с тестированием, распараллеливанием, поддержкой?lair
08.10.2015 10:19То, что вы описываете — это нарушение SRP, и не является специфичной особенностью value types (собственно, вы и пример-то приводите от сущности).
smilegs
08.10.2015 10:56Извиняюсь за глупые вопросы. Возможно, автор статьи (@vkhorikov) подскажет.
Но можно привести пример POCO с логикой, чтоб он не нарушал SRP, и не был VO?
Будет ли единственным отличием POCO и VO иммутабельность последнего?
Можно ли вообще обойтись без термина POCO, ограничившись DTO (объекты без логики) и VO (объекты с тривиальной логикой)?lair
08.10.2015 11:20+1Но можно привести пример POCO с логикой, чтоб он не нарушал SRP, и не был VO?
Любая бизнес-сущность.
Вы смешиваете объекты, находящиеся в разных областях. POCO — это термин из области реализации, означающий, что объект не наследует от какого-нибудьComponent
илиEntity
, навязанного инфраструктурой. Value Object и Entity — это термины из области моделирования (например, DDD), и они означают разделение объектов в модели на имеющие свою идентичность (сущности), и не имеющие таковой (объекты-значения).smilegs
08.10.2015 11:29Спасибо.
Коментарий, проясняет суть вопроса лучше чем статья :)
Сказано не в обиду автору, автор тоже млодец.
konsoletyper
08.10.2015 10:50методы по добавлению товаров в БД
Нет, добавление товаров в БД — это не часть бизнес-логики, а часть инфраструктуры. См. паттерн repository
отправки их по сети
Если отправка осуществляется путём выставления какого-нибудь REST API, то это часть presentation layer, который выше слоем над domain model. Если задача обратная — надо постучать в удалённый сервис, например, по SOAP, то пишется интерфейс ProductConsumer, реализация которого может его отправлять куда и как угодно (и скорее всего, даже не пишется вручную, а генерится каким-нибудь CXF или Jersey). При этом сам класс Product вполне может содержать методы, которые решают, когда товар отправлять и нужно ли его вообще отправлять, что уже является частью бизнес-логики (потому что такие вещи формулируются заказчиком).
получения связанных сущностей типа всех покупателей
А вот это пожалуйста. В JPA есть аннотация ManyToMany. При этом это не будет «логикой», это делает JPA-провайдер (который является частью инфраструктуры), а внутри — просто Set, Map или List.
mird
07.10.2015 20:35Простите, но все три определения ввел в обиход Мартин Фаулер. И он в них вложил вполне определенный смысл. POCO — если переводить на русский «Старый добрый C# объект» объекты в объектно-ориентированном программировании содержат не только данные но и поведение.
smilegs
08.10.2015 10:24Не знал, что Plain переводится как «старый» :)
mird
08.10.2015 10:55Не поверите, как старый переводится Old. Plain Old C#(или CLR) Object.
mird
08.10.2015 11:00Если переводить пословно получится что-то типа
«Обыкновенный старый C# объект». Но, учитывая что вводилось это понятие как клевое название для обычных классических объектов, перевод «Старый добрый C# объект» тут вполне в тему и звучит более по-русскиsmilegs
08.10.2015 11:15Если вдаваться в трудности перевода, то перевод слова Plain как «Плоский», более отражает суть и поведение объекта — простой и без наследования.
Плоский объект для меня — добрый объект :) Кому как удобнее :)lair
08.10.2015 11:20более отражает суть и поведение объекта — простой и без наследования.
Так наследование-то там вполне может быть.
mird
08.10.2015 14:18+1А автор не вкладывал сюда значение «плоский». Он вкладывал значение обычный (то есть не навязанный никаким фреймворком). При этом если доменная модель предполагает для конкретного объекта наследование — наследование будет, просто это наследование должно быть по причине такого домена, а не потому что наш фреймворк требует чтобы все сущности были отнаследованы от абстрактного класса Component.
smilegs
08.10.2015 11:02не знаю, чем я думал :) хотел написать «добрый» :) типа Plain переводится как «добрый».
vkhorikov
07.10.2015 20:36+2Мнение, которое вы высказали, и послужило причиной написания этой статьи.
Value Object — иммутабельные объекты
Это верно.
Они как бы строительные кирпичики для Entity
Это тоже верно.
Никакой логики в них быть не должно
Вот это уже неверно. Value Object-ы вполне себе могут содержать логику. Более того, Эванс и ко рекомендуют помещать доменную логику именно в Value Object-ы там где это возможно, т.к. работать с ними проще из-за их неизменяемости. Хороший пример Value Object-а — DateTime в .NET. Неизменямый тип данных с большим количеством логики внутри.
POCO (POJO, PODS) — это принцип организации данных
POCO — это в первую очередь принцип, который говорит о том, что при моделировании предметной области следует использовать настолько простые классы, насколько возможно. Это понятие никак не связано с DTO (кроме того, что DTO тоже не следует наследовать от тяжеловесных компонент).
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? Ну да, сравнивается и копируется по значению.vkhorikov
08.10.2015 01:06Как правило, Value Object-ы (так же как и Entities) не используются для передачи данных между приложениями. В целом, в простых случаях (как например в вашем) Value Object-ы действительно можно использовать как DTO, но опять же — это до тех пор, пока в них не скопилось достаточно логики.
Хорошей практикой считается изолировать домен приложения. На практике это означает, что доменные классы (Entities and Value Objects) не должны знать о том, как они сериализуются. В более-менее сложных случаях этого трудно достичь если сериализовывать их напрямую.
caballero
вообще Entity подразумевают уникальный ключ. Если и он совпадает то это таки одна сущность. По крайней мере одна бизнес-сущность.
lair
Суррогатные ключи часто не считают свойством сущности, а натуральных может и не быть.
Nagg
Можно даже сказать, что суррогатный ключ в каком-то виде — протекающая абстракция.
konsoletyper
В каком-то смысле да. Обычно, в 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, т.к. для этого нужно дополнительно инжектить репозиторий. С другой стороны известно, что инжектить нужно не очень много, а если пришлось инжектить много зависимостей — у вас неправильный дизайн.