value object что это

DTO vs POCO vs Value Object

Определения 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-ы могут содержать логику и обычно они не используются для передачи информации между приложениями.

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

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

Другой хороший пример анти-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 что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

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

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

value object что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

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

Заключение

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

Источник

PHP Profi

Квест → Как хакнуть форму

DDD в PHP: Value Object или Объект-Значение Перевод

Определение Мартина Фаулера:

Небольшой простой объект, как деньги или диапазон дат, равенство которых не основано на идентичности

Объект-Значение (Value Object) — это объект, который представляет собой понятие из предметной области. В DDD (Domain Driven Development — разработка на основе предметной области, или предметно-ориентированное программирование) важно то, что Value Object поддерживает и обогащает Единый Язык вашей Предметной Области. Это не только примитивы, которые представляют собой некоторые значения, — они являются полноправными гражданами Предметной Области, которые формируют поведение вашего приложения.

Преимущества использования Value Objects

Самое главное заключается в том, что эти объекты отражают язык, на котором вы разговариваете с другими разработчиками — когда вы говорите «Место»(Location) все знают, что это значит. Второе преимущество заключается в том, что Value Object может валидировать значение — подходит оно или нет для того, чтобы создать такой объект.

Третьим преимуществом является то, что вы можете полагаться на тип — вы знаете, что если такой Value Object был принят в качестве аргумента, он будет всегда в допустимом состоянии и вам не нужно беспокоиться об этом. И также Value Object может содержать некоторые специализированные методы, которые имеют смысл только в контексте этого значения и могут быть расположены в этом объекте (не нужно создавать странные классы-утилиты).

Пример Value Object

В качестве примера Value Object-а, который является распространённым для всех веб-приложений, я создал EmailAddress:

Одержимость примитивами

Вы можете неохотно относиться к использованию объектов в качестве контейнеров для примитивных значений, но подобные вещи описаны как «код с запашком» под названием «Одержимость примитивами»:

Одержимость примитивами — это использование примитивных типов данных для представления сущностей Предметной области. Например, мы используем String для представления сообщения, Integer в качестве суммы денег, или Struct/Dictionary/Hash для представления конкретного объекта.

Использование Value Object-ов является одной из стратегий борьбы с этим запахом. Ключевая идея здесь — это собрать поведение данных вокруг своего объекта. В противном случае такие действия будут разбросаны по всему коду, что может привести к ненужной сложности и заставит вас относиться с опаской к значениям, переданным в методы.

Неизменяемость

Очень важно помнить и понимать, что Value Object является неизменяемым. Почему это такое важное понятие? Задумайтесь о реальности вокруг вас и какие значения вы используете — число один, красный цвет и так далее. Вы не можете изменить эти значения — это не имеет смысла, менять красный цвет на зеленый, и продолжать называть его красным — это больше не красный, и называние зеленого красным будет путать людей. Таким же образом Value Object в вашем приложении должны быть неизменными.

Резюме

Value Object-ы являются важными гражданами вашей Предметной Области, которые отражают его концепции. Убедитесь, что вы приложите соответствующее поведение к таким объектам, которое впоследствии будет делать код более организованным и лучше передавать реальность, потому что ключевым аспектом и целью объектно-ориентированного программирования является моделирование реального мира.

Источник

Entity vs Value Object: полный список отличий

Типы эквивалентности

Чтобы обозначить разницу между entities и value objects, нам необходимо определить три типа эквивалентности (equality), которые вступают в силу как только мы пытаемся сравнить два объекта друг с другом.

Reference equality (ссылочная эквивалентность) означает, что два объекта равны в случае если они ссылаются на один и тот же объект в куче:

value object что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

Вот как мы можем проверить ссылочную эквивалентность в C#:

Identifier equality (эквивалентность идентификаторов) подразумевает, что у класса присутствует Id поле. Два объекта такого класса будут равны если они имеют одинаковый идентификатор:

value object что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

И, наконец, струкрурная эквивалентность означает полную эквивалентность всех полей двух объектов:

value object что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

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

На практике это означает, что объекты-значения не имеют поля-идентификатора и если два экземпляра одного объекта-значения обладают одинаковым набором атрибутов, мы можем считать их взаимозаменяемыми. В то же время, даже если данные в двух сущностях полностью одинаковы (за исключением Id поля), они не являются одной и той же сущностью.

Вы можете думать об этом в том же ключе, в котором вы думаете о двух людях, носящих одинаковое имя. Мы не считаем их одним и тем же человеком из-за этого. Они оба обладают внутренней (неотъемлимой) идентичностью. В то же время, если у нас есть 1 рубль, нам все равно та же ли это монета, что была у нас вчера. То тех пор пока эта монета является монетой ценностью в 1 рубль, мы не против заменить ее другой, точно такой же. Концепция денег в таком случае является объектом-значением.

Жизненный цикл

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

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

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

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

Неизменяемость

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

Обязательная неизменяемость объектов-значений принимается не всеми программистами. Некоторые считают, что этот гайдлайн не такой строгий, как предыдущие и объекты-значения могут быть изменяемыми в некоторых случаях. Я тоже придерживался этого мнения некоторое время назад.

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

Это несложное мысленное упражнение делает неизменяемость неотъемлемой частью объектов-значений. Если мы принимаем, что они имеют нулевой жизненный цикл, в том смысле что они являются всего лишь слепком какого-либо состояния и ничем более, то мы должны также принять, что они могут представлять только один вариант этого состояния.

Это приводит нас к следующиему правилу: если вы не можете сделать объект-значение неизменяемым, значит этот класс не является объектом-значением.

Как распознать объект-значение в доменной модели?

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

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

Не смотря на отсутствие объективных показателей, мы все же можем использовать некоторые приемы для того, чтобы отнести концепт к сущностям или объектам-значениям. Мы уже обсуждали три вида эквивалентности: если мы можем заменить один экземпляр класса другим с теми же свойствами, то это хороший знак того, что перед нами объект-значение.

Более простая версия того же приема заключается в том, чтобы мысленно сравнить класс с целочисленным значением (integer). Вам как разработчику безразлично является ли цифра 5 той же цифрой, которую вы использовали в предыдущем методе. Все пятерки в вашем приложении одинаковы, не зависимо от того, как они были созданы. Это делает тип integer по сути объектом-значением. Теперь, задайте себе вопрос: выглядит ли этот класс как integer? Если ответ да, то это объект-значение.

Как хранить объекты-значения в базе данных?

Предположим, что мы имеем два класса в доменной модели: сущность Person и объект-значение Address:

Как будет выглядить структура БД в этом случае? Решение, которое приходит в голову в такой ситуации — создать отдельные таблицы для обоих классов:

value object что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

Такой дизайн, не смотря на полную валидность с точки зрения БД, имеет два недостатка. Во-первых, таблица Address содержит идентификатор. Это означает, что нам будет необходимо ввести отдельное поле Id в класс Address чтобы работать с такой таблицей корректно. Это, в свою очередь, означает, что мы добавляем классу некоторую идентичность. А это уже нарушает определение объекта-значения.

Второй недостаток здесь в том, что мы потенциально можем отделить объект-значение от родителькой сущности. Address может жить собственной жизнью, т.к. мы можем удалить Person из БД без удаления соответствующей строки Address. Это будет нарушением другого правила, говорящего о том, что время жизни объектов-значений должно полностью зависеть от времени жизни их родительских сущностей.

Наилучшим решением в данном случае будет «заинлайнить» поля из таблицы Address в таблицу Person:

value object что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

Это решит обе проблемы: Address не будет иметь собственного идентификатора и его время жизни будет полностью зависеть от времени жизни сущности Person.

Этот дизайн также имеет смысл если вы мысленно замените все поля, относящиеся к Address, единственным integer, как я предложил ранее. Создаете ли вы отдельную таблицу для каждого целочисленного значения в вашей доменной модели? Конечно нет, вы просто включаете его в родительскую таблицу. Те же правила применимы к объектам-значениям. Не создавайте отдельную таблицу для объектов-значений, просто включите их поля в таблицу сущности, к которой они принадлежат.

Предпочитайте объекты-значения сущностям

В вопросе объектов-значений и сущностей важное значение имеет следующее правило: всегда предпочитайте объекты-значения сущностям. Объекты-значения неизменяемы и из-за этого с ними крайне просто работать. В идеале, вы всегда должны стремиться включить большинство бизнес-логики в объекты-значения. Сущности в таких ситуациях будут служить обертками над ними и представлять более высокоуровневую функциональность.

Также, может случиться так, что концепт, который вы изначально видели как сущность, на самом деле является объектом-значением. К примеру, вы могли изначально представить класс Address в вашем коде как сущность. Он может иметь собственный Id и отдельную таблицу в БД. После некоторого размышления вы замечаете, что в вашей предметной области адреса на самом деле не имеют собственной идентичности и могут использоваться взаимозаменяемо. В этом случае, не стесняйтесь рефакторить вашу доменную модель, конвертируйте сущность в объект-значение.

Источник

Domain Driven Design: Value Objects и Entity Framework Core на практике

Под катом много кода.

Немного теории

Ядром архитектуры Domain Driven Design является Домен — предметная область, к которой применяется разрабатываемое программное обеспечение. Здесь находится вся бизнес-логика приложения, которая обычно взаимодействует с различными данными. Данные могут быть двух типов:

Entity может содержать другие Entity и VO. В состав VO могут быть включены другие VO, но не Entity.

Таким образом, логика домена должна работать исключительно с Entity и VO — этим гарантируется его консистентность. Базовые типы данных, такие как string, int и т.д. зачастую не могут выступать в качестве VO, потому что могут элементарно нарушить состояние домена — что в рамках DDD является почти катастрофой.

Пример. Набивший всем оскомину в различных руководствах класс Person часто показывают вот так:

Просто и наглядно — идентификатор, имя и возраст, где же тут можно ошибиться?

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

С точки зрения языка программирования, 5 — вполне нормальное целое число, аналогично и пустая строка. А вот домен уже находится в некорректном состоянии.

Переходим к практике

К этому моменту мы знаем, что VO должен быть иммутабельным и содержать значение, допустимое для бизнес-логики.

Иммутабельность достигается инициализацией readonly свойства при создании объекта.
Проверка допустимости значения происходит в конструкторе (Guard clause). Саму проверку желательно сделать доступной публично — для того, чтобы, другие слои могли провалидировать данные поступившие от клиента (тот же браузер).

Давайте создадим VO для Name и Age. Дополнительно немного усложним задачу — добавим PersonalName, объединяющий в себе FirstName и LastName, и применим это к Person.

Таким образом, мы не можем создать Person без полного имени или возраста. Также мы не можем создать “неправильное” имя или “неправильный” возраст. А хороший программист обязательно проверит в контроллере поступившие данные с помощью методов Name.IsValid(“John”) и Age.IsValid(35) и в случае некорректных данных — сообщит об этом клиенту.

Если мы возьмем за правило везде в модели использовать только Entity и VO, то убережем себя от большого количества ошибок — неправильные данные просто не попадут в модель.

Persistence

Теперь нам нужно сохранить наши данные в хранилище данных и получить их по запросу. В качестве ORM будем использовать Entity Framework Core, хранилище данных — MS SQL Server.

DDD четко определяет: Persistence — это подвид инфраструктурного слоя, поскольку скрывает в себе конкретную реализацию доступа к данным.

Домен ничего не должен знать про Persistence, по этому определяет только интерфейсы репозиториев.

А Persistence содержит в себе конкретные реализации, конфигурации маппинга, а также объект UnitOfWork.

Существует два мнения, стоит ли создавать репозитории и Unit of Work.

Но домен в DDD не зависит от хранения данных и используемого ORM — это всё тонкости имплементации, которые инкапсулированы в Persistence и никого больше не интересуют. Если мы предоставляем DbContext в другие слои, то тут же раскрываем детали имплементации, намертво завязываемся на выбранную ORM и получаем DAL — как основу всей бизнес-логики, а такого быть не должно. Грубо говоря, домен не должен заметить изменение ORM и даже потерю Persistence как слоя.

Итак, интерфейс репозитория Persons, в домене:

и его реализация в Persistence:

Казалось бы, ничего сложного, но есть проблема. Entity Framework Core “из коробки” работает только с базовыми типами (string, int, DateTime и т.д.) и ничего не знает про PersonalName и Age. Давайте научим EF Core понимать наши Value Objects.

Configuration

Для конфигурирования Entity в DDD больше всего подходит Fluent API. Атрибуты не подходят, так как домен не должен ничего знать про нюансы маппинга.

Создадим в Persistence класс с базовой конфигурацией PersonConfiguration:

и подключим его в DbContext:

Mapping

Тот раздел, ради которого и написан этот материал.

В данный момент есть два более-менее удобных способа маппинга нестандартных классов к базовым типам — Value Conversions и Owned Types.

Value Conversions

Эта фича появилась в Entity Framework Core 2.1 и позволяет определять конвертацию между двумя типами данных.

Напишем конвертер для Age (в этом разделе весь код — в PersonConfiguration):

Простой и лаконичный синтаксис, но не обошлось без недостатков:

Здесь есть условие по возрасту, но EF Core не сможет его преобразовать в SQL запрос и, дойдя до Where(), загрузит всю таблицу в память приложения и, только потом, с помощью LINQ, выполнит условие p.Age.Value > age.Value.

В общем, Value Conversions — простой и быстрый вариант маппинга, но нужно помнить о такой особенности работы EF Core, иначе, в какой то момент, при запросе в большие таблицы, память может закончиться.

Owned Types

Owned Types появились в Entity Framework Core 2.0 и пришли на замену Complex Types из обычного Entity Framework.

Давайте сделаем Age как Owned Type:

Неплохо. А еще Owned Types не имеют некоторых недостатков Value Conversions, а именно пунктов 2 и 3.

2. Возможно конвертировать одно свойство в несколько колонок в таблице и наоборот

То, что нужно для PersonalName, хотя синтаксис уже немного перегружен:

3. EF Core умеет преобразовывать LINQ выражение с этим свойством в SQL запрос.
Добавим сортировку по LastName и FirstName при загрузке списка:

Такое выражение будет корректно преобразовано в SQL запрос и сортировка выполняется на стороне SQL сервера, а не в приложении.

Конечно, есть и недостатки.

С другой стороны — это такие детали реализации, которые можно опустить, но, опять же, забывать не стоит. Трекинг изменений влияет на производительность. Если с выборками единичных Entity (например, по Id) или небольших списков это не заметно, то с выборкой больших списков “тяжелых” Entity (много VO-свойств) — просадка в производительности будет весьма заметной именно из-за трекинга.

Presentation

Мы разобрались как реализовать Value Objects в домене и репозитории. Пришло время все это использовать. Создадим две простейшие странички — со списком Person и формой добавления Person.

Код контроллера без Action методов выглядит так:

Добавим Action для получения списка Person:

Ничего сложного — загрузили список, создали Data-Transfer Object (PersonModel) на каждый

Person и отправили в соответствующую View.

value object что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

Гораздо интереснее добавление Person:

Здесь присутствует обязательная валидация входящих данных:

Если этого не делать, то при создании VO с некорректным значением будет выкинуто ArgumentException (помним про Guard Clause в конструкторах VO). С проверкой же гораздо легче отправить пользователю сообщение, что какое то из значений неверное.

value object что это. Смотреть фото value object что это. Смотреть картинку value object что это. Картинка про value object что это. Фото value object что это

Здесь нужно сделать небольшое отступление — в Asp Net Core есть штатный способ валидации данных — с помощью атрибутов. Но в DDD такой способ валидации не является корректным по нескольким причинам:

Заключение

Я привел примеры реализации Value Objects в общем и нюансы маппинга в Entity Framework Core. Надеюсь, что материал пригодится в понимании того, как применять элементы Domain Driven Design на практике.

Полный исходный код проекта PersonsDemo — GitHub

В материале не раскрыта проблема взаимодействия с опциональными (nullable) Value Objects — если бы PersonalName или Age были не обязательными свойствами Person. Я хотел это описать в данной статье, но она и так вышла несколько перегруженной. Если есть интерес к этой проблематике — пишите в комментариях, продолжение будет.

Фанатам “красивых архитектур” в общем и Domain Driven Design в частности очень рекомендую ресурс Enterprise Craftsmanship.

Также использовалась официальная документация по Owned Types и Value Conversions.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *