Член: Використовуйте унікальні ідентифікатори та об’єкт домену


10

Після декількох корисних відповідей на те, чи слід використовувати об’єкт домену або унікальний ідентифікатор як параметр методу / функції тут Ідентифікатор проти об’єкта домену як параметр методу , у мене є аналогічне запитання: члени (попереднє обговорення питань не вдалося виконати покриваємо це). Які плюси та мінуси використання унікальних ідентифікаторів як член проти об’єкта як член. Я прошу посилатися на такі сильно набрані мови, як 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, який може, в свою чергу, залежати від самого об’єкта Користувача, створюючи нескінченний ланцюг. Чи є загальне рішення цієї проблеми як для RDBMS, так і для No-SQL (якщо вони різні)?

Виходячи з деяких відповідей на даний момент, перефразовуючи моє запитання: (із використанням ідентифікаторів, які повинні бути в упакованих типах) 1) Завжди використовувати ідентифікатори? 2) Завжди використовувати об'єкти? 3) Використовуйте ідентифікатори, коли є ризик рекурсії при серіалізації та десеріалізації, але використовуйте об'єкти інакше? 4) Що-небудь ще?

EDIT: Якщо ви відповідаєте на те, що Об'єкти слід використовувати завжди або в деяких випадках, будь ласка, не забудьте відповісти на найбільше занепокоєння, яке інші відповідачі опублікували => Як отримати дані з БД


1
Дякуємо за гарне запитання, з нетерпінням чекайте наступного питання з цікавістю. Трохи соромно, що ваше ім'я користувача "user18151", люди з таким іменем користувачів дехто ігнорує :)
bjfletcher

@bjfletcher Дякую У мене було таке бурхливе сприйняття, але мені ніколи не приходило в голову чому!
0fnt

Відповіді:


7

Об'єкти домену як ідентифікатори створюють деякі складні / тонкі проблеми:

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

Якщо ви зберігаєте об’єкти як ключі, це зробить серіалізацію об'єктного графіка надзвичайно складним. Ви отримаєте stackoverflowпомилки, роблячи наївну серіалізацію в JSON або XML через рекурсію. Тоді вам доведеться написати спеціальний серіалізатор, який перетворює фактичні об'єкти для використання їх ідентифікаторів, а не серіалізації об’єктного об'єкта та створення рекурсії.

Передайте об'єкти для безпеки типу, але зберігайте лише ідентифікатори, тоді ви можете мати метод аксесуара, який ледачий завантажує відповідний об'єкт, коли він викликається. Кешування другого рівня забезпечить наступні дзвінки.

Тонкі довідкові витоки:

Якщо ви використовуєте об’єкти домену в таких конструкторах, ви створите кругові посилання, що буде дуже важко дозволити відновленню пам'яті для об'єктів, які активно не використовуються.

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

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

idПовинна бути повністю непрозорим ідентифікатором , який не несе ніякої інформації про те, що вона ідентифікує. Але він повинен запропонувати певну перевірку того, що він є дійсним ідентифікатором у своїй системі.

Сирі види порушують це:

int, longі Stringє найбільш часто використовуваними типами сировини для ідентифікаторів в системі RDBMS. Існує довга історія практичних причин, які датуються десятиліттями, і всі вони є компромісами, які або вкладаються в економію, spaceабо в економію, timeабо в обидва.

Послідовні ідентифікатори - найгірші правопорушники:

Коли ви використовуєте послідовний ідентифікатор, ви за замовчуванням запакуєте тимчасову семантичну інформацію в ідентифікатор. Що непогано, поки його не використовують. Коли люди починають писати ділову логіку, яка сортує або фільтрує семантичну якість ідентифікатора, тоді вони створюють світ болю для майбутніх утримувачів.

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

Це робить неможливим і створення розподіленої системи даних, оскільки 12437379123вона не є єдиною в усьому світі. Шанси, що інший вузол в розподіленій системі створить запис з однаковим числом, майже гарантовані, коли ви отримаєте достатньо даних у системі.

Потім хаки починають працювати навколо нього, і вся річ перетворюється на купу пареного безладу.

Ігнорування величезних розподілених систем ( кластерів ) стає повним кошмаром, коли ви починаєте обмінюватися даними також з іншими системами. Особливо, коли інша система не під вашим контролем.

У вас з'являється та сама проблема, як зробити свій ідентифікатор глобальним.

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

UUIDможе страждати від усіх перерахованих вище проблем, залежно від того, якими Versionви користуєтесь.

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

Version 2використовує користувачів UIDабо GIDdomian, 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 є абсолютно непрозорими, і саме так і має бути.

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

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

Спадщина

Якщо у вас є вихідні типи і ви не можете їх змінити, ви все одно можете їх абстрагувати у своєму коді.

Використання Version 3/5з UUIDвас може пройти в Class.getName()+ String.valueOf(int)вигляді byte[]і має непрозорий контрольний ключ , який recreatable і детерміновані.


Мені дуже шкода, якщо я не був зрозумілий у своєму питанні, і я відчуваю себе все гірше (або насправді добре), бо це така чудова і добре продумана відповідь, і ви явно витратили на це час. На жаль, це не відповідає моєму питанню, можливо, воно заслуговує на власне питання? "Що слід пам’ятати під час створення поля id для мого об’єкта домену"?
0fnt

Я додав явне пояснення.

Зрозумів зараз. Дякуємо, що витратили час на відповідь.
0fnt

1
До речі, поколільні сміттєзбірники AFAIK (які я вважаю, що є домінуючою системою GC в наші дні) не повинні мати особливих труднощів у кругових посиланнях GC'ing.
0fnt

1
якщо C-> A -> B -> Aі Bвводиться в цей Collectionчас, Aі всі його діти все ще доступні, ці речі не є абсолютно очевидними і можуть призвести до тонких витоків . GCє найменшою з проблем, серіалізація та десеріалізація графіка - це кошмар складності.

2

Так, переваги в будь-якому випадку є, і є також компроміс.

List<int>:

  • Збережіть пам’ять
  • Швидше ініціалізація типу User
  • Якщо ваші дані надходять із реляційної бази даних (SQL), вам не доведеться отримувати доступ до двох таблиць, щоб отримати користувачів, просто Usersтаблицю

List<Book>:

  • Доступ до книги відбувається швидше від користувача, вона була попередньо завантажена в пам'ять. Це добре, якщо ви можете дозволити собі триваліший пуск, щоб швидше здійснити наступні операції.
  • Якщо ваші дані надходять із бази даних зберігання документів, наприклад HBase чи Cassandra, то значення прочитаних книг, ймовірно, є в записі користувача, тому ви могли легко отримати книги ", коли ви там отримували користувача".

Якщо у вас немає пам’яті чи процесорних проблем, з якими я б пішов List<Book>, код, який використовує Userекземпляри, буде чистішим.

Компроміс:

Під час використання Linq2SQL код, сформований для сутності Користувача, матиме EntitySet<Book>ледачий завантаження під час доступу до нього. Це повинно зберігати чистий код і маленький екземпляр користувача (слід пам'яті).


Якщо припустити якесь кешування, вигода попереднього завантаження буде нульовою. Я не використовував Cassandra / HBase, тому не можу про них говорити, але Linq2SQL - це дуже специфічний випадок (хоча я не бачу, як ліниве завантаження завадить нескінченному ланцюжковій справі навіть у цьому конкретному випадку, і в загальному випадку)
0fnt

У прикладі Linq2SQL ви дійсно не отримуєте переваги від продуктивності, просто чистіший код. Коли ви отримуєте від одного до багатьох об'єктів із сховища документів, як-от Cassandra / HBase, переважна більшість часу на обробку витрачається на пошук запису, тому ви, можливо, також отримаєте всю безліч об'єктів, перебуваючи там (книги, в цей приклад).
ytoledano

Ти впевнений? Навіть якщо я зберігаю Книгу та Користувачів окремо нормалізованими? Для мене це виглядає так, що це повинно бути лише затримкою мережі додаткових витрат. У будь-якому випадку, як можна обробляти справу RDBMS загалом? (Я відредагував питання, щоб це чітко зазначити)
0fnt

1

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

Ідентифікатори використовуються в DTO s.
Посилання на об'єкти зазвичай використовуються в об'єктах доменної логіки / бізнес-логіки та інтерфейсу користувача.

Це загальна архітектура для великих, достатньо підприємливих проектів. У вас з’являться картографи, які перекладають і перетворюють ці два об’єкти.


Дякую, що завітали і відповіли. На жаль, хоча я розумію різницю завдяки посиланням на вікі, я ніколи цього не бачив на практиці (при цьому я ніколи не працював з великими довгостроковими проектами). Чи є у вас приклад, коли один і той же об’єкт був представлений двома способами для двох різних цілей?
0fnt

ось актуальне питання щодо картографування: stackoverflow.com/questions/9770041/dto-to-entity-mapping-tool - і є такі критичні статті, як це: rogeralsing.com/2013/12/01/…
herzmeister

Дуже корисно, дякую. На жаль, я досі не розумію, як би працювало завантаження даних із циркулярними зверненнями? наприклад, якщо користувач посилається на книгу, а книга посилається на того самого користувача, як би ви створили цей об’єкт?
0fnt

Подивіться на шаблон сховища . Ви матимете BookRepositoryта і UserRepository. Ви завжди будете викликати myRepository.GetById(...)або подібні, і сховище буде або створювати об’єкт і завантажувати його значення з сховища даних, або отримувати його з кеша. Також дочірні об’єкти здебільшого ледачі навантажені, що також не дозволяє мати справу з прямими круговими посиланнями під час будівництва.
герцмайстер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.