Участник: использовать уникальные идентификаторы против объекта домена

10

После пары полезных ответов о том, должен ли я использовать объект домена или уникальный идентификатор в качестве параметра метода / функции здесь Идентификатор против объекта домена в качестве параметра метода , у меня возникает похожий вопрос re: members (предыдущее обсуждение вопросов не удалось покрыть это). Каковы плюсы и минусы использования уникальных идентификаторов в качестве члена против объекта в качестве члена. Я спрашиваю в отношении строго типизированных языков, таких как Scala / C # / Java. Должен ли я иметь (1)

User( id: Int, CurrentlyReadingBooksId: List[Int])
Book( id: Int, LoanedToId: Int )

или (2), предпочтительнее (1) После прохождения: должны ли мы определить типы для всего?

User( id: UserId, CurrentlyReadingBooksId: List[ BookId] )
Book( id: BookId, LoanedToId: UserId )

или (3)

User( id: Int, CurrentlyReadingBooks: List[Book]) 
Book( id: Int, LoanedTo: User)

Хотя я не могу думать о преимуществах наличия объекта (3), одно из преимуществ наличия идентификаторов (2) и (1) состоит в том, что когда я создаю объект User из БД, мне не нужно создавать объект Book, который может, в свою очередь, зависеть от самого объекта User, создавая бесконечную цепочку. Существует ли общее решение этой проблемы как для RDBMS, так и для No-SQL (если они разные)?

Основываясь на некоторых ответах, перефразирующих мой вопрос: (с использованием идентификаторов, которые должны быть в упакованных типах) 1) Всегда использовать идентификаторы? 2) Всегда использовать объекты? 3) Использовать идентификаторы, когда существует риск рекурсии в сериализации и десериализации, но использовать объекты иначе? 4) Что-нибудь еще?

РЕДАКТИРОВАТЬ: Если вы отвечаете, что объекты должны использоваться всегда или в некоторых случаях, пожалуйста, убедитесь, что ответили на самую большую проблему, которую другие ответчики опубликовали => Как получить данные из БД

0fnt
источник
1
Спасибо за хороший вопрос, с нетерпением ждем этого с интересом. Немного обидно, что ваше имя пользователя "user18151", люди с таким именем игнорируются некоторыми :)
bjfletcher
@bjfletcher Спасибо. У меня было это ноющее восприятие, но мне никогда не приходило в голову, почему!
0

Ответы:

7

Доменные объекты как идентификаторы создают некоторые сложные / тонкие проблемы:

Сериализация / десериализации

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

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

Тонкие эталонные утечки:

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

Идеальная ситуация:

Непрозрачные идентификаторы против int / long:

idДолжна быть полностью непрозрачным идентификатором , который не несет никакой информации о том, что она идентифицирует. Но он должен предложить некоторую проверку того, что он является действительным идентификатором в своей системе.

Сырые типы ломают это:

int, longИ Stringнаиболее часто используемые типы сырья для идентификаторов в системе RDBMS. Практические причины, которые датируются десятилетиями, имеют долгую историю, и все они являются компромиссами, которые либо вписываются в сбережения, spaceлибо в сбережения, timeлибо в оба варианта.

Последовательные идентификаторы являются худшими нарушителями:

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

String поля являются проблематичными, потому что наивные дизайнеры будут упаковывать информацию в содержимое, обычно также во временную семантику.

Это также делает невозможным создание распределенной системы данных, поскольку 12437379123она не является уникальной в глобальном масштабе. Вероятность того, что другой узел в распределенной системе создаст запись с таким же номером, в значительной степени гарантируется, когда вы получаете достаточно данных в системе.

Затем вокруг него начинают работать хаки, и все это превращается в кучу дымящегося беспорядка.

Игнорирование огромных распределенных систем ( кластеров ) становится полным кошмаром, когда вы начинаете пытаться обмениваться данными с другими системами. Особенно, когда другая система не находится под вашим контролем.

Вы сталкиваетесь с точно такой же проблемой, как сделать свой идентификатор глобально уникальным.

UUID был создан и стандартизирован по причине:

UUIDможет страдать от всех проблем, перечисленных выше, в зависимости от того, что Versionвы используете.

Version 1использует MAC-адрес и время для создания уникального идентификатора. Это плохо, потому что несет семантическую информацию о месте и времени. Это само по себе не проблема, это когда наивные разработчики начинают полагаться на эту информацию для бизнес-логики. Это также приводит к утечке информации, которая может быть использована при любых попытках вторжения.

Version 2использует пользователей UIDили GIDи domian UIDили GUIвместо этого время Version 1так же плохо, как Version 1для утечки данных и риска этой информации для использования в бизнес-логике.

Version 3аналогично, но заменяет MAC-адрес и время MD5хэшем некоторого массива byte[]из чего-то, что определенно имеет семантическое значение. Существует нет утечки данных, чтобы беспокоиться, byte[]не может быть восстановлено из UUID. Это дает вам хороший способ детерминированного создания UUIDэкземпляров формы и какого-либо внешнего ключа .

Version 4 основывается только на случайных числах, что является хорошим решением, оно не несет в себе никакой семантической информации, но оно не детерминировано воссоздано.

Version 5так же, как, Version 4но использует sha1вместо md5.

Ключи домена и ключи данных транзакций

Я предпочитаю идентификаторы объекта домена, использовать Version 5или, Version 3если Version 5по каким-либо техническим причинам, запрещено использовать .

Version 3 отлично подходит для данных транзакций, которые могут быть распределены по многим машинам.

Если вы не ограничены пространством, используйте UUID:

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

Version 3,4,5 полностью непрозрачны, и именно так они и должны быть.

Вы можете иметь один столбец в качестве первичного ключа с, UUIDа затем вы можете иметь составные уникальные индексы для того, что было бы естественным составным первичным ключом.

Хранения не должно быть CHAR(36)либо. Вы можете хранить UUIDв собственном поле байтов / битов / чисел для данной базы данных, пока она еще индексируется.

наследие

Если у вас есть необработанные типы, и вы не можете их изменить, вы все равно можете абстрагировать их в своем коде.

Использование Version 3/5of of of UUIDвы можете передать Class.getName()+ String.valueOf(int)как a byte[]и иметь непрозрачный ссылочный ключ, который может быть восстановлен и детерминирован.


источник
Мне очень жаль, если я не поняла в своем вопросе, и я чувствую себя все хуже (или на самом деле хорошо), потому что это такой замечательный и продуманный ответ, и вы явно потратили на него время. К сожалению, это не соответствует моему вопросу, может быть, оно заслуживает отдельного вопроса? «Что следует иметь в виду при создании поля id для моего доменного объекта»?
0
Я добавил явное объяснение.
Понял сейчас. Спасибо, что потратили время на ответ.
0fnt
1
Кстати, сборщики мусора поколений AFAIK (которые, я считаю, является доминирующей системой GC в наши дни) не должны испытывать особых затруднений в циклических ссылках GC.
0
1
если C-> A -> B -> Aи Bпомещено в Collectionтогда, Aи все его дети все еще достижимы, эти вещи не совсем очевидны и могут привести к незначительным утечкам . GCЭто наименьшая из проблем, сериализация и десериализация графа - это сложный кошмар.
2

Да, в любом случае есть и преимущества, и компромисс.

List<int>:

  • Сохранить память
  • Быстрая инициализация типа User
  • Если ваши данные поступают из реляционной базы данных (SQL), вам не нужно обращаться к двум таблицам, чтобы получить пользователей, только к Usersтаблице

List<Book>:

  • Доступ к книге быстрее от пользователя, книга была предварительно загружена в память. Это хорошо, если вы можете позволить себе более длительный запуск, чтобы ускорить последующие операции.
  • Если ваши данные поступают из базы данных хранилища документов, такой как HBase или Cassandra, тогда значения прочитанных книг, скорее всего, указаны в записи пользователя, так что вы могли легко получить книги «пока вы там получали пользователя».

Если у вас нет проблем с памятью или процессором, я бы пошел с List<Book> , с которыми , код, использующий Userэкземпляры, будет чище.

Компромисс:

При использовании Linq2SQL код, сгенерированный для сущности User, будет иметь EntitySet<Book>ленивую загрузку при доступе к нему. Это должно сохранить ваш код в чистоте, а экземпляр User - маленьким (след памяти).

ytoledano
источник
Если предположить какое-то кэширование, преимущество предварительной загрузки будет нулевым. Я не использовал Cassandra / HBase, поэтому не могу говорить о них, но Linq2SQL - это очень специфический случай (хотя я не вижу, как отложенная загрузка предотвратит случай бесконечного сцепления даже в этом конкретном случае и в общем случае)
0
В примере с Linq2SQL вы действительно не получаете никакого выигрыша в производительности, только более чистый код. При получении сущностей «один ко многим» из хранилища документов, такого как Cassandra / HBase, подавляющее большинство времени обработки тратится на поиск записи, так что вы могли бы также получить все множество сущностей, находясь там (книги в этот пример).
итоледано
Ты уверен? Даже если я храню Книгу и Пользователи отдельно нормализованы? На мой взгляд, это должно быть связано только с дополнительной задержкой в ​​сети. В любом случае, как обрабатывать случай СУБД в целом? (Я отредактировал вопрос, чтобы четко это упомянуть)
0
1

Краткое и простое правило:

Идентификаторы используются в DTO .
Ссылки на объекты обычно используются в объектах уровня логики домена / бизнес-логики и пользовательского интерфейса.

Это обычная архитектура в крупных, достаточно корпоративных проектах. Вы будете иметь картографы, которые переводят туда и сюда эти два вида объектов.

herzmeister
источник
Спасибо, что заглянули и ответили. К сожалению, хотя я понимаю разницу благодаря вики-ссылке, я никогда не видел этого на практике (при условии, что никогда не работал с большими долгосрочными проектами). Был бы у вас пример, когда один и тот же объект был представлен двумя способами для двух разных целей?
0
вот актуальный вопрос, связанный с отображением: stackoverflow.com/questions/9770041/dto-to-entity-mapping-tool - и есть критические статьи, подобные этой: rogeralsing.com/2013/12/01/…
herzmeister
Действительно полезно, спасибо. К сожалению, я до сих пор не понимаю, как будет работать загрузка данных с циклическими ссылками? Например, если Пользователь ссылается на Книгу, а Книга ссылается на одного и того же пользователя, как бы вы создали этот объект?
0
Посмотрите на шаблон репозитория . Вы будете иметь BookRepositoryи UserRepository. Вы всегда будете вызывать myRepository.GetById(...)или делать что-то подобное, и хранилище либо создаст объект и загрузит его значения из хранилища данных, либо получит его из кэша. Кроме того, дочерние объекты в основном загружаются с отложенной загрузкой, что также предотвращает необходимость иметь дело с прямыми циклическими ссылками во время построения.
Герцмейстер