Звернення уваги до того, що первинні ключі не є частиною вашого домену бізнесу


25

Майже за будь-яких обставин первинні ключі не є частиною домену вашого бізнесу. Звичайно, у вас можуть бути важливі об’єкти, що звертаються UserNameдо користувачів, з унікальними індексами ( для користувачів або OrderNumberзамовлень), але в більшості випадків немає жодної необхідності в явній ідентифікації об'єктів домену за одним значенням або набором значень комусь, але можливо адміністративний користувач. Навіть у тих виняткових випадках, особливо якщо ви використовуєте глобальні унікальні ідентифікатори (GUID) , вам сподобається або хочете використовувати альтернативний ключ, а не викривати сам первинний ключ.

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

  1. Наївно, об'єкти передачі даних (DTO), які походять виключно з комбінацій доменних моделей, не матимуть первинних ключів
  2. Вхідний DTO не буде мати первинний ключ

Отже, чи можна впевнено сказати, що якщо ви дійсно збираєтеся залишатися чистими та викорінювати первинні ключі у вашій моделі домену, ви повинні бути готові мати можливість обробляти кожен запит з точки зору унікальних показників цього первинного ключа?

По-іншому, яке з наведених рішень є правильним підходом до роботи з ідентифікацією конкретних об'єктів після видалення ПК в доменних моделях?

  1. Вміти ідентифікувати об'єкти, з якими потрібно мати справу за іншими атрибутами
  2. Повернення первинного ключа назад у DTO; тобто виключаючи ПК при зіставленні від стійкості до домену, потім рекомбінації ПК при зіставленні з домену в DTO?

EDIT: Давайте зробимо це конкретне.

Скажімо , моя модель домену , VoIPProviderякий включає в себе такі області , як Name, Description, URL, а також посилання подобається ProviderType, PhysicalAddressі Transactions.

Тепер скажімо, що я хочу створити веб-сервіс, який дозволить пільговим користувачам керувати VoIPProviders.

Можливо, зручний для користувача ідентифікатор в цьому випадку марний; врешті-решт, провайдерами VoIP є компанії, чиї назви мають тенденцію бути чіткими в комп'ютерному розумінні і навіть досить чіткими в людському розумінні з бізнес-причин. Тому може бути достатньо сказати, що унікальність VoIPProviderповністю визначається (Name, URL). Отже, скажімо, мені потрібен метод, PUT api/providers/voipщоб привілейовані користувачі могли оновити VoIPпровайдерів. Вони надсилають a VoIPProviderDTO, що включає в себе багато, але не всі поля з поля VoIPProvider, включаючи деякі згладжування потенційно. Однак я не можу прочитати їхні думки, і їм все одно потрібно сказати, про якого постачальника ми говоримо.

Здається, у мене є 2 (можливо 3) варіанти:

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

1
Поміркуйте: часто спілкування з експертами по домену збіднюється, коли сурогатний ПК використовується, коли існує хороший бізнес-ключ. Здається, ми закінчимо роботу в рамках ORM, а не навпаки.
Тулан Кордова

@ user61852 добре незалежно від ORM, навіть якщо ви дійсно низького рівня, вам все одно потрібен первинний ключ при реалізації шару бази даних. Отже, я погоджуюся, що сурогатний ПК надає вам переваги перед фактичним ПК, який використовується певним механізмом збереження, але якщо ця ПК дійсно представляє діловий об'єкт, який має сенс, він обов'язково є унікальним і, отже, має принаймні одну унікальну властивість, пов’язану з бізнесом. це, ні?
tacos_tacos_tacos

1
Усі переваги сурогатів пов'язані з комп’ютером і жодними.
Tulains Córdova

2
@ user61852: Я згоден на 100% (я написав щось інше?). Для засобів зв’язку використовуйте «діловий ключ». Додайте унікальні контракти також для будь-якого бізнес-ключа. Але уникайте використання ділових ключів для реальної реалізації посилань на ваші бази даних.
Док Браун

2
ділові ключі вічно унікальні - доки їх немає. Якщо ви використовуєте бізнес-ключі як основні, коли це відбувається, то зміна правил бізнесу порушує більше матеріалів.
пс

Відповіді:


31

Ось як це ми вирішуємо (адже більше 15 років, коли навіть термін "дизайн, керований доменом" не був винайдений):

  • під час відображення доменної моделі в реалізації бази даних або моделі класу на певній мові програмування, у вас є просте, послідовне правило типу "для кожного об'єкта домену, відображеного в реляційній таблиці, первинним ключем є" Name NameID ".
  • цей первинний ключ є абсолютно штучним, він завжди має один і той же тип, і жодне ділове значення - просто сурогатний ключ
  • "графічна версія" вашої моделі домену (та, яку ви використовуєте для розмови зі своїми експертами з домену) не містить первинних ключів. Ви не піддаєте їх безпосередньо експертам (але ви піддаєте їх тим, хто реально реалізує код для системи).

Отже, коли вам потрібен первинний ключ для технічних цілей (наприклад, зіставлення відносин до бази даних), у вас є такий доступний, але до тих пір, поки ви не хочете "бачити це", змініть рівень свого абстрагування на "модель експертів домену ". І вам не доведеться підтримувати "дві моделі" (одна з ПК та одна без); натомість підтримуйте лише модель без ПК та використовуйте генератор коду для створення DDL для вашої БД, який автоматично додає ПК відповідно до правил відображення.

Зауважте, що це не забороняє додавати будь-які "бізнес-ключі", як додатковий "OrderNumber", окрім сурогату OrderID. Технічно ці бізнес-ключі стають альтернативними ключами при зіставленні вашої бази даних. Просто уникайте їх використовувати для створення посилань на інші таблиці, завжди віддайте перевагу використанню сурогатних ключів, якщо можливо, це полегшить вам справи.

На ваш коментар: використання сурогатного ключа для ідентифікації записів не є операцією, пов’язаною з бізнесом, це суто технічна операція. Щоб це було зрозуміло, подивіться на ваш приклад: доки ви не визначаєте додаткові унікальні контранти, можна було б мати два об'єкти VoIPProvider з однаковою комбінацією (ім'я, URL), але різними VoIPProviderID.


Що з кроком вилучення об’єкта домену з повернутого об’єкта збереження (сутності, моделі таблиці рядків або будь-якого іншого), переходу до DTO і повернення його назад і повернення до стійкості? Це щойно робиться через сурогатний ключ (тобто бізнес-орієнтоване визначення унікальності), який вимагає вирішення щодо кожної наполегливої ​​операції?
tacos_tacos_tacos

1
@tacos_tacos_tacos: давайте дотримуємось вашого прикладу VoIPProvider. Я б фактично додавав "VoIPProviderID" до вашого DTO, принаймні на "стороні реалізації" (якщо у вас є також графічна версія для ваших експертів з домену, я, мабуть, не показуватиму її там). Для цілей оновлення стандартним способом ідентифікації конкретного VoIPProvider повинен бути "VoIPProviderID", який ви отримали, витягуючи дані з бази даних. Якщо користувачі вашого API віддають перевагу ідентифікації (ім’я, URL), надайте це додатково. ...
Док Браун

... і якщо ефективність здається справжньою, вимірюваною проблемою, ви також можете десь захопити кешування відображення (ім'я, URL) у VoIPProviderID. Але я б не рекомендував проводити таку оптимізацію заздалегідь, передчасно.
Док Браун

1
Природні ключі іноді здаються справді переконливими, але надто легко їх спалити (наприклад, "ого, зараз у мене є кілька орендарів, і це вже не унікально").
Кейсі

1
@corsiKa: в контексті того, що запитувала ОП, я б настійно рекомендував мати чисто автогенерований ключ "OrderID" (який не друкується в будь-якій квитанції, а використовується лише для внутрішніх речей, таких як посилання на бази даних), і окремий бізнес-ключ "OrderNumber" (який може, наприклад, містити щось на кшталт поточного року, який можна використовувати для сортування та фільтрування, які згодом можна змінити / виправити та які можна надрукувати на квитанціях). ОП попросило "Дизайн, керований доменом", "Номер замовлення" є частиною доменної моделі, в той час як "Порядок замовлення" - це лише деталь реалізації.
Док Браун

4

Вам потрібно вміти ідентифікувати багато об'єктів за допомогою унікального індексу, і це не те, що є первинним ключем (або принаймні означає, що такий присутній).

Є унікальні індекси, щоб ви могли додатково обмежувати вашу схему БД, а не як оптову заміну ПК. Якщо ви не виставляєте ПК, тому що вони некрасиві, але натомість виставляєте унікальний ключ ... ви насправді не робите нічого іншого. (Я припускаю, що ви тут не змішуєте ПК та стовпець посвідчення?)


Коли я хочу здійснити операцію із залученням певного екземпляра доменного об'єкта, який зберігається, мені потрібно мати можливість включити в DTO або явно (через якийсь ключ, або первинний, або альтернативний зручний користувальницький ідентифікатор, унікальний до цієї таблиці, а також може бути індексом у таблиці) або неявно (через якусь комбінацію полів, значення яких однозначно ідентифікують конкретний запис) ... і, крім того, у практичному сенсі мені потрібно було б відправити в DTO якась форма відстеження змін, якби я робив це іншим способом (OriginalVal vs NewVal для всіх полів, що ідентифікують запис), ні?
tacos_tacos_tacos

Чи не чіткий v неявний питання однакова різниця? Ви можете мати ПК, який охоплює кілька стовпців, як і унікальний індекс. Я не бачу різниці між ними у ваших цілях.
gbjbaanb

Звичайно, у нас може бути ПК, наприклад, у кількох стовпцях. Але для мене це витікає щось про базу даних (сховище), яке не має нічого спільного з серцем і душею, суб'єктом господарювання. Якщо якийсь кордон полів суб'єкта господарської діяльності надійшов на PK для БД, то чудово. Але це не повинно бути навпаки, чи не так?
tacos_tacos_tacos

Ти це переосмислюєш. Унікальний індекс - це стільки ж артефакт схеми БД, як і ПК. Подумайте про це так - ПК - це лише перший (або первинний) унікальний індекс. його "особливий", тому що вам потрібен лише 1 такий індекс.
gbjbaanb

Щоправда, але будь-який значимий доменний об'єкт повинен бути чітко ідентифікований принаймні в одному з його бізнес-сфер, ні? Той факт, що це визначає індекс у БД, скоріше з міркувань продуктивності, ніж для того, щоб легко запитувати БД ... Я вважаю за краще однокалонний ПК над унікальним індексом у 6 стовпців, і вони справді служать різним цілям - PK (або індекс з невеликою кількістю полів) також доступний для зручності DBA / DBD, правда?
tacos_tacos_tacos

4

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

Як приклад, скажімо, що я хочу редагувати повідомлення в додатку; як програма дізнається, яке повідомлення я хочу редагувати, не додаючи первинний ключ? Редагування об'єктів відбувається постійно, і робити це без клавіш майже неможливо. Але якщо у вас є об'єкти, які не слід редагувати, тоді пропустіть ключ, якщо ви думаєте, що це відволікає увагу, але наявність первинних ключів може покращити продуктивність тут.


Ну, повідомлення крайній приклад, але навіть тоді ми знали б MessageSender, MessageRecipient, TimeSent- це повинно бути унікальним.
tacos_tacos_tacos

1
@tacos_tacos_tacos, то як ви створюєте FKs для інших таблиць? Це повинен бути MessageSenderId, який, ймовірно, відображається в таблиці користувачів на UserId. Ви не хочете використовувати UserName як ключ між таблицями, оскільки це може змінитися і стане кошмаром технічного обслуговування. Ось чому ви, як правило, приєднуєтесь лише до таблиць за допомогою первинних ключів, а не іншого стовпця (звичайно, є винятки). Цю структуру db все-таки потрібно виконувати. Тепер ви завжди можете перейти до моделі CQRS для свого додатка ... у цьому випадку правила змінюються. Особливо, якщо ви також використовуєте пошук подій.
CaffGeek

4

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

Я бачу, що ви відповіли з коментарем: MessageSender, MessageRecipient, TimeSent (для повідомлення). Ви можете ВИНАГИ мати неоднозначність таким чином (наприклад, із створеними системою повідомленнями, що спрацьовують на те, що трапляється часто). І як ви збираєтесь тут перевірити MessageSender та MessageRecipient? Припустимо, ви перевіряєте їх за допомогою FirstName, прізвища, DateOfBirth, ви, врешті-решт, зіткнетесь із ситуацією, коли у вас в той же день народиться 2 людини з таким самим іменем. Не кажучи вже про те, що ви зіткнетесь з ситуацією, коли у вас є повідомлення з іменем tacostacostacos-America-1980-Doc Brown-France-1965-23/5/2014-11:43:54.003UTC+200. Це чудовисько імені, і ви все ще не маєте гарантій, що у вас буде лише 1 з них.

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

Не потрібно показувати свій ідентифікатор користувачеві. Ви можете приховати це (на увазі, якщо потрібно, через URL-адресу).

Ще одна причина, чому ПК є настільки корисним, - це те, що ви можете зробити з вищезазначеного: ПК робить це так, що вам не доведеться змушувати комп'ютер інтерпретувати створений користувачем код. Якщо китайський користувач використовує ваш код і вводить купу китайських символів, ваш код раптом не повинен мати можливість працювати з ними внутрішньо, але він може просто використовувати Посібник, створений системою. Якщо у вас є арабський користувач, який вводить арабською мовою, вашій системі не доведеться з цим справлятися внутрішньо, але в основному може ігнорувати, що вони там є.

Як говорили інші, Посібник - це те, що можна зберігати всередині фіксованого розміру. Ви знаєте, з чим працюєте, і це те, що можна використовувати універсально. Не потрібно створювати правила дизайну щодо того, як ви створюєте певний ідентифікатор і зберігаєте його. Якщо ваша система приймає лише перші 10 літер імені, вона не бачить різниці між Майклом Гуггенхаймером і Майклом Гугштейном, і це буде переплутати ці 2. Якщо ви відріжете її будь-якої довільної довжини, ви можете зіткнутися з плутаниною. Якщо ви обмежите введення користувача, ви можете зіткнутися з проблемами з обмеженнями користувачів.

Коли я дивлюся на існуючі системи, такі як Dynamics CRM, вони також використовують внутрішній ключ (ПК) для користувача, щоб викликати один запис. Якщо у користувача є запит, який не включає ідентифікатор, він повертає масив можливих відповідей і дозволяє користувачеві вибирати його. Якщо є неоднозначність, вони нададуть вибір користувачеві.

Нарешті, це також позбавлення безпеки через незрозумілість. Якщо ви не знаєте ідентифікатора запису, ваш єдиний варіант - це здогадатися. якщо ідентифікатор легко здогадатися (оскільки інформація, з якої він виготовлений, є загальнодоступною), будь-хто може змінити його. Ви навіть можете примусити користувача змінити його за допомогою класичних методів CSRF або XSS. Тепер, очевидно, ваша безпека вже повинна була враховувати та пом'якшувати її, перш ніж публікувати пряму версію, але все ж слід ускладнити можливі зловживання.


1

Випускаючи ідентифікатор для зовнішньої системи, ви повинні надати лише URI, або, альтернативно, ключ або набір ключів, які мають ті ж властивості, що і URI, а не відкривати первинний ключ бази даних безпосередньо (звідси далі я посилаюся на і URI, або ключ або набір ключів, які мають ті ж властивості, що і URI, як і URI, інакше кажучи, URI нижче не обов'язково означає RFC 3986 URI).

URI може містити або не містити первинний ключ об'єкта, а він може, а може і не бути, складається з альтернативних ключів. Це насправді не має значення. Важливо те, що лише системі, яка генерує URI, дозволено розділяти або комбінувати URI, щоб сформувати розуміння того, що таке згаданий об'єкт. Зовнішні системи завжди повинні використовувати URI як непрозорий ідентифікатор. Не має значення, чи може користувач людини ідентифікувати, що одна частина URI насправді є сурогатним ключем бази даних або що він складається з декількох бізнес-ключів, зв'язаних разом, або що це насправді база64 цих значень. Це не мають значення. Важливо те, що зовнішня система не повинна вимагати, щоб розуміти, що означає ідентифікатор для використання ідентифікатора. Зовнішні системи ніколи не повинні вимагати розбору компонентів в ідентифікаторі або поєднання ідентифікатора з іншими ідентифікаторами для посилання на щось у вашій системі.

Використання GUID відповідає деяким із цих критеріїв, проте ідентифікатор, як GUID, може бути важко перезавантажити назад в об'єкт навіть у вашій системі, тому ключі, непрозорі для навіть вашої системи, як GUID, слід використовувати лише у випадку, коли клієнт фактично аналізує URI / ідентифікатор створює ризик для безпеки.

Повернувшись до прикладу VoIP, скажіть, що провайдера VoIP можна однозначно визначити або (VoIPProviderID), або (Ім'я, URL), або (GUID). Коли зовнішня система потребує оновлення провайдера VoIP, вона може просто передати PUT / провайдера / by-id / 1234 або PUT /provider/foo-voip/bar-domain.comабо PUT /3F2504E0-4F89-41D3-9A0C-0305E82C3301і ваша система зрозуміє, що зовнішня система хоче оновити VoIPProvider. Ці URI генеруються вашою системою, і лише ваша система повинна розуміти, що всі вони означають одне і те ж. Зовнішня система повинна просто ставитись до того, що є в URI, як в основному PUT <whatever>.

Припустимо, у вас є дані для різних провайдерів VoIP, які зберігаються в різних таблицях з різними схемами (таким чином, абсолютно різний набір ключів ідентифікує кожного постачальника VoIP, залежно від того, у якій таблиці вони зберігаються). Якщо у вас є URI, до них можна отримати доступ рівномірно зовнішньою системою, без огляду на те, як ваша система визначає конкретного VoIP-провайдера. Для зовнішньої системи все це лише непрозорий покажчик.

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


0

Мені доведеться націлити це дико неточне та наївне твердження:

Можливо, зручний для користувача ідентифікатор в цьому випадку марний; врешті-решт, провайдерами VoIP є компанії, чиї назви мають тенденцію бути чіткими в комп'ютерному розумінні і навіть досить чіткими в людському розумінні з ділових причин.

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

Тоді ми вступаємо в той факт, що назви компаній навіть не є віддаленими унікальними, як це показує орієнтир Apple проти Apple .

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

Для довідки, я віддаю перевагу тому, як джанго справляється з цим:

class VoipProvider(models.Model):
    name=fields.TextField()
    address=fields.TextField()

class Customer(models.Model):
    name=fields.TextField()
    address=fields.TextField()
    voipProvider=fields.ForeignKeyField(VoipProvider)

Таким чином, до інформації провайдера клієнта можна отримати доступ у коді, використовуючи:

myCustomer.voipProvider.name #returns the name of the customers VOIP Provider.

Хоча Первинний / Зовнішній ключі не видно, вони є там, і вони можуть бути використані для доступу до предметів, але вилучені з них.


Ви абсолютно праві, але я думаю, я мав на увазі, що "в деяких областях, можливо, є природний набір цінностей, які завжди унікальні". Якщо вони змінюються, але вони все ще унікальні, вони все одно ідентифікують один запис.
tacos_tacos_tacos

0

Я думаю, що ми часто все ще неправильно дивимось на це питання з позиції БД: о, природного ключа немає, тому нам потрібно створити сурогатний ключ. О ні, ми не можемо виставити сурогатний ключ назад в об'єкти домену, це є витоку тощо.

Однак іноді кращим є таке ставлення: якщо об’єкт бізнесу (домену) не має природного ключа, то, можливо, йому слід надати його. Це дворазове питання ділового домену: По-перше, речі потребують ідентичності навіть за відсутності баз даних. По-друге, хоча ми намагаємось робити вигляд, що наполегливість є якоюсь абстрактною ідеєю, невидимою для домену, реальність полягає в тому, що наполегливість все ще є бізнес-концепцією. Очевидно, є проблеми, коли вибраний природний ключ не підтримується БД в якості первинного ключа (наприклад, GUID на деяких системах) - у цьому випадку вам потрібно буде додати сурогатний ключ.

Таким чином, ви опиняєтесь у дуже подібному місці, наприклад, у вашого клієнта є ціле ідентифікатор, але замість того, що ви відчуваєте себе погано, оскільки це просочилося з БД у домен, ви почуваєтесь щасливими, оскільки бізнес погодився, що всім клієнтам буде призначено Ідентифікатор, і ви зберігаєте це в БД, як і повинно. Ви все ще можете ввести сурогат, наприклад, для підтримки перейменування ідентифікатора клієнта.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.