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

в 4:56, , рубрики: .net, C#, DDD, Entity, Value Object, Программирование, Проектирование и рефакторинг

Тема отличий таких понятий как Entity (Сущность) и Value Object (Объект-Значение) из Domain-Driven Design не нова. Тем не менее, я не смог найти статью с полным списком их отличий, так что решил написать свою.

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

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

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

image

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

object object1 = new object();
object object2 = object1;
bool areEqual = object.ReferenceEquals(object1, object2); // возвращает true

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

image

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

image

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// Entity
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}
 
// Value Object
public class Address
{
    public string City { get; set; }
    public string ZipCode { get; set; }
}

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

image

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

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

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

image

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

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

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

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

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

Заключение

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

Английская версия статьи: Entity vs Value Object (DDD)

Автор: vkhorikov

Источник

Поделиться новостью

* - обязательные к заполнению поля