DDD: первичные ключи (идентификаторы) и ORM (например, NHibernate)
Почему считается, что у OK есть поле Id в объектах домена?
Я видел несколько решений, которые предоставляют базовый класс Id и Id на основе GetHashCode/Equals.
Мое понимание модели домена заключается в том, что она должна содержать только вещи, связанные с доменом. Хотя в редких случаях (отслеживаемые заказы) идентификаторы имеют смысл, большую часть времени они не предоставляют ничего, кроме простого способа ссылки на объекты в DB/on UI.
Я также не вижу преимуществ Equals/GetHashCode, так как реализация Identity Map должна гарантировать, что ссылочное равенство равно. Идентификатор равенства в любом случае.
Странно, я не могу легко найти то, что другие люди думают по этому вопросу, поэтому я спрашиваю его здесь. Каково общее мнение об использовании идентификаторов, не связанных с доменом, в объектах домена? И есть ли какие-либо проблемы с NHibernate, если я не добавляю идентификаторы в свои объекты домена?
UPDATE:
Спасибо за ответы.
Некоторые из них предполагают, что идентификатор - единственный способ для ORM выполнить обновление БД. Я не думаю, что это так. ORM уже отслеживает все сущности, загруженные из БД, поэтому он должен легко получить идентификатор внутри, когда ему это нужно.
ОБНОВЛЕНИЕ 2:
Ответ на правосудие и аналогичные вопросы:
Что делать, если у нас есть веб-приложение и нужен способ ссылки на объект между сеансами? Как edit/resource/id?
Ну, я вижу это как особую потребность в ограниченном пользовательском интерфейсе/среде, а не в необходимости модели домена. Для этого сценария достаточно иметь сервис приложений или репозиторий с методом GetIdentitity (в соответствии с методом Load (identity)).
Ответы
Ответ 1
Я просто могу поговорить о NHibernate. Там вам нужно поле для первичного ключа, это зависит от вас, если вы берете бизнес-данные (не рекомендуется) или суррогатный ключ без каких-либо деловых целей.
Типичные сценарии:
- значение автоинкремента, генерируемое базой данных
- guid, созданный NHibernate
- guid, созданный бизнес-логикой
Существует большое преимущество иметь уникальный идентификатор.. Когда вы передаете свой объект вокруг, например, клиенту и обратно на сервер, вы не можете полагаться на идентификатор памяти. У вас может быть даже несколько экземпляров в памяти одного и того же бизнес-экземпляра. Бизнес-экземпляр идентифицируется идентификатором.
Сравнение на основе Id - это вопрос вкуса. Лично мне нравится простой и надежный код, а сравнение на основе идентификаторов просто и надежно, в то время как глубокое сравнение всегда немного рискованно, подвержено ошибкам, не поддается контролю. Таким образом, вы в конечном итоге получаете оператор ==, сравнивающий идентификатор памяти и Equals, сравнивающий идентификатор бизнеса (и базы данных).
NHibernate - это менее интрузивный способ сопоставить модель класса с реляционной базой данных, которую я знаю. В нашем крупном проекте у нас есть первичные ключи и свойства версии (они используются для оптимистической блокировки). Вы можете утверждать, что это навязчиво, потому что оно не используется для бизнес-логики.
NH не требует наличия этих свойств. (однако для этого требуется одно из свойств в качестве первичного ключа.) Но учтите:
- Это просто работает лучше, например. пессимистическая блокировка возможна только с соответствующим свойством,
- быстрее: int id работает лучше во многих базах данных, чем другие типы данных
- и проще: использование свойств со смыслом для бизнеса не рекомендуется по нескольким причинам.
Итак, зачем делать вашу жизнь труднее, чем нужно?
Изменить: Просто прочитайте документацию и обнаружили, что NHibernate выполняет не свойство id в постоянном классе! Если у вас нет указателя, вы не можете использовать некоторые функции. Рекомендуется иметь его, он просто упрощает вашу жизнь.
Ответ 2
Как правило, у вас не будет одной настоящей модели домена. Вы будете иметь бесчисленные параллельные и последовательные сеансы, которые должны быть скоординированы, и каждая из них содержит в себе собственную миниатюрную изолированную транзакционную модель домена. У вас не будет одинаковых экземпляров объектов между двумя одновременными или между двумя последовательными сеансами. Но вам нужно будет каким-то образом убедиться, что вы можете перемещать информацию из одной сессии в другую.
Примером, конечно, является пара URL/resource/list и /resource/show/id URL (соответствующая соответствующей паре действий контроллера в asp.net mvc). Переходя от одного к другому, есть два полностью независимых сеанса, но им нужно общаться (через HTTP и браузер) друг с другом.
Даже без использования базы данных объекты вашей модели домена по-прежнему будут нуждаться в какой-то форме, поскольку у вас все еще будет множество миниатюрных изолированных моделей домена, а не одна истинная модель домена; и вы будете иметь все эти модели домена в независимых сеансах одновременно и один за другим.
Идентичность объекта не достаточна, поскольку объекты не совпадают между сеансами, хотя базовые концептуальные объекты, которые они представляют, являются одним и тем же объектом.
Ответ 3
В Nh вы не можете использовать ссылочное равенство для отдельных экземпляров. Это заставляет вас использовать NH в клиентах, которые хотели бы использовать это поведение (на мой взгляд, это правильное поведение, NH не зависит от магии!).
В веб-приложениях или на основе сеанса, где вы можете перейти на db в рамках одного ISession, я думаю, что я прав, говоря, что вы можете положиться на ссылочное равенство. Но в сценарии смарт-клиента, или, более конкретно, в любом сценарии, в котором два ISessions делят экземпляр (один загружается из или добавляется в db, затем передается ко второму), вам нужно поле (-ы) db id, хранящееся вместе с экземпляр.
второй сеанс ниже не знал, что экземпляр, который он передал, уже вставлен и требует обновления, и даже если вы изменили его на обновление, я предполагаю, что я прав, думая, что он не собирается знать идентификатор базы данных класса.
class SomeEntityWithNoId
{
public blah etc {}
}
class dao
{
void DateMethod()
{
var s1 = sessionFactory.OpenSession();
var instance = new SomeEntityWithNoId();
instance.Blah = "First time, you need to insert";
s1.Save(s1); //now instance is persisted, has a DB ID such as identity or Hilo or whatever
s1.close();
var s2 = sessionFactory.OpenSession();
s2.Blah = "Now update, going to find that pretty hard as the 2nd session won't know what ID to use for the UPDATE statement";
s2.Update(instance); //results in boom! exception, the second session has no idea *how* to update this instance.
}
}
Если вас беспокоит поле (и) идентификатора, которое вы считаете правильным в мышлении, в значительной степени бесполезно для чего-либо, кроме проблем с сохранением, вы можете сделать их частными, поэтому, по крайней мере, это поведение и семантика инкапсулируются. Вы можете обнаружить, что этот подход вносит некоторые трения в TDD.
Я думаю, что вы правильно относитесь к картам ID по NH. Если экземпляр является переходным, когда он впервые подключен к сеансу, ваш тип не должен хранить свой идентификатор базы данных в поле. Я думаю, что сессия будет знать, как управлять этим отношением с db. Я уверен, что вы можете опробовать тест, чтобы доказать это поведение, p
Ответ 4
Вы должны иметь это так или иначе, чтобы ORM мог вернуться в базу данных для выполнения обновлений; Я предполагаю, что он может быть закрытым и скрытым в базовом классе, но этот вид нарушает концепцию POCO.
Ответ 5
Предполагая, что ваш идентификатор является вашим основным ключом, вы не сможете сохранить данные обратно в БД без него.
Что касается equals и GetHashCode, то они предназначены для упорядочения в списках, где вы создаете пользовательский порядок.
Спасибо за ответы.
Некоторые из них предполагают, что идентификатор - единственный способ для ORM выполнить обновление БД. Я не думаю, что это так. ORM уже отслеживает все сущности, загруженные из БД, поэтому он должен легко получить идентификатор внутри, когда ему это нужно.
Я был бы очень удивлен, если бы это было так. Обычно он создает оператор insert только из свойств, которые вы определили.
Ответ 6
Нет другого способа получить один и тот же объект без идентификации. Например, люди имеют номер социального страхования или личный код или что-то уникальное, и мы можем идентифицировать их им.
Вы можете использовать естественную идентификацию, но это подвержено ошибкам.
Просто переименуйте идентификатор в идентификатор. Он выглядит лучше в модели домена.
Сущности называются объектами, поскольку они имеют идентификатор.
Ответ 7
Я обычно включаю Id в домен в качестве базового класса Entity. Я не знаю другого способа использовать ORM.
Объект в домене уже уникален по своей ссылке, использование идентификатора для определения уникальности/равенства на самом деле не так уж и отличается на практике. До сих пор я не сталкивался с серьезным побочным эффектом этого подхода.
Ответ 8
Вы также можете использовать несколько полей для установления идентификатора, называемого составными ключами. Это потребует больше работы с вашей стороны.
Узнайте о них здесь