Довідкові значення бази даних у бізнес-логіці


43

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

1 Apple 
2 Banana 
3 Grapes

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

Що стосується правил бізнесу / логіки домену, які рекомендації щодо присвоєння логіки конкретним значенням. Скажіть, якщо користувач вибрав Виноград, я хочу виконати якусь додаткову задачу, який найкращий спосіб посилатися на значення Винограду:

// Hard coded name
if (user.FavouriteFruit.Name == "Grapes")

// Hard coded ID
if (user.FavoriteFruit.ID == 3) // Grapes

// Duplicate the list of fruits in an enum
if (user.FavouriteFruit.ID == (int)Fruits.Grapes)

чи щось інше?

Оскільки, звичайно, FavouriteFruit буде використовуватися у всій програмі, список може бути доданий або відредагований.

Хтось може вирішити, що вони хочуть, щоб "Виноград" було перейменовано на "Виноград", і це, звичайно, порушило б варіант з жорстким кодом.

Ідентифікатор з твердим кодом не зовсім зрозумілий, хоча, як показано, ви можете просто додати коментар, щоб швидко визначити, про який предмет.

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

У будь-якому випадку, заздалегідь дякую за будь-які коментарі чи пропозиції.


1
Дякую всім: пропозиції та загальні поради дуже корисні. @RemcoGerlich Ваша ідея розділити проблеми рядка, що використовується для відображення, і окремого як коду пошуку для більш читабельного коду дуже добре.
Кейт

1
Я збираюся дати @Mike Nakis ваші ідеї попередньо завантажених об'єктів, хоча це здається найкращим з обох світів.
Кейт

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

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

1
Гммм ... Якщо є ідентифікатор 1: 1 відношення до рядка, то це зайве, а наявність обох - безглуздо. Рядок може слугувати як ключ БД так само добре, як ціле число. MyApplication.Grape.IDзаїкається, так би мовити "Apple" - це не "Red_Apple" більше, ніж ID 3 - це також 4. Отже, потенціал перейменування "Apple" на "Red_Apple" не має більшого сенсу, ніж заявляти, що 3 - це 4 (а може бути навіть і 3). Суть перерахунку полягає в абстрагуванні чисельної ДНК. Тож, можливо, настав час по- справжньому розв’язати довільні реляційні ключі БД, які буквально не мають сенсу в бізнес-моделях.
radarbob

Відповіді:


31

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

if( user.FavouriteFruit.ID == MyApplication.Grape.ID )

Що тільки що тут сталося, це те, що я, очевидно, завантажив увесь ряд Grapeпам'яті, тому я маю його ідентифікатор готовий використовувати для порівняння. Якщо ви використовуєте об'єктно-реляційне картографування (ORM), це виглядає ще краще:

if( user.FavouriteFruit == MyApplication.Grape )

(Тому я називаю це "Попередньо завантаженими об'єктами".)

Отже, що я роблю, це те, що під час запуску я завантажую всі свої таблиці «перерахування» (невеликі таблиці, такі як дні тижня, місяці року, стать тощо) в основний клас домену додатків. Я завантажую їх по імені, тому що, очевидно, MyApplication.Grapeповинен отримати рядок під назвою "Виноград", і я запевняю, що кожен з них знайдений. Якщо ні, у нас є гарантована помилка під час запуску, яка є найменш злоякісною з усіх помилок під час запуску.


17
Я не погоджуюся з відповіддю, але вважаю, що обов'язково "Уникайте рядків і магічних констант за будь-яку ціну" не погоджуєтесь з рештою відповіді, яка фактично вимагає, щоб у вас було принаймні одне місце, де використовуються магічні константи або рядки. у заповненні "попередньо завантажених об'єктів". Це помітно, я думаю, тому що існують способи цілком уникнути "рядків і магічних констант", хоча це зазвичай більш заплутано, ніж це варто ...
svidgen

2
@svidgen не погоджуєтесь ви, що існує принципова різниця між розсіюванням прив'язки до імені по всьому місцю порівняно з прив'язкою до імені лише один раз, щоб завантажити вміст запису, що має те саме ім'я, і ​​робити це лише при запуску, де помилки виконання майже такі ж доброякісні, як і помилки компіляції? У будь-якому разі, способи уникнути навіть найменшого зв’язування по імені завжди цікаві, незважаючи на зганьбленість, про яку ви згадали, тому мені було б цікаво почути, що ви маєте на увазі.
Майк Накіс

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

1
@ user469104 Різниця полягає в тому, що ідентифікатори можуть змінюватися, і додаток все одно буде правильно завантажувати всі рядки та виконувати всі порівняння правильно. Крім того, ви можете безкоштовно переробити код і перейменовувати рядки будь-яким способом, і єдине місце, де вам потрібно буде шукати речі, це виправити - це запуск програми, і це, як правило, дуже очевидно: Grape = fetchRow( Fruit.class, NameColumn, "Grape" ); І якщо ви робити щось неправильно, AssertionErrorзаповіт дозволить вам знати.
Майк Накіс

1
@grahamparks не більше, ніж була enumб чарівною струною. Сенс полягає в концентрації всіх прив'язуючих імен лише в одному місці , валідація всього цього під час запуску та безпека типу .
Майк Накіс

7

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

Зазвичай я розбиваю обидва обов'язки на окремі поля:

id  code    description
 1  grape   Grapes
 2  apple   Apple

Якщо опис може змінитися (але не "Виноград" на "Банан"), але код заборонено змінювати ніколи.

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

Також, як часто хтось насправді редагує "Виноград" на "Виноград"? Можливо, нічого з цього не потрібно.


8
Я не думаю, що ще більше надмірності - це відповідь ...
Роббі Ді

4
Я також розглядав цей варіант, і я його спробував, але саме так і закінчилося: в якийсь момент "яблуко" довелося диференціювати на "green_apple" і "red_apple". Але оскільки "яблуко" вже використовувалося в безлічі місць у коді, я не зміг його перейменувати, тому мені довелося мати "яблуко" та "зелений_аппл". І як результат, Шелдон в мені заважав мені спати кілька ночей, поки я не зайшов туди і не відремонтував усе на "Попередньо завантажені об'єкти". (див. мою відповідь.)
Майк Накіс

1
Мені напевно подобаються ваші попередньо завантажені об’єкти, але якщо ваше "яблуко" розмежоване, чи не доведеться вам все-таки перебирати все, який би метод ви не вибрали?
RemcoGerlich

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

1
@MikeNakis і Refactoring - це по суті пошук і заміна на всій базі коду, замінюючи Fruit.Apple на Fruit.GreenApple. Якщо я використовую значення Hardcoded String, я б здійснив пошук і заміна на всій кодовій базі, щоб замінити "яблуко" на "green_apple", що приблизно однакове. - Рефакторинг просто відчуває себе краще, тому що IDE робить Заміну.
Фалько

4

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

Я бачив кілька моделей:

  • Перераховує + за замовчуванням, щоб захистити від абсолютно нового запису бази даних, що руйнує день вашої програми
  • Кодування дій, які потрібно виконати (логіка biz) в самій базі даних. У багатьох випадках це дуже можливо, оскільки багато логіки можуть бути використані повторно. Реалізація логіки повинна бути в програмі.
  • Додаткові атрибути / стовпці в базі даних, щоб позначити абсолютно нове значення як "для ігнорування" в програмі, поки програма не буде розгорнута вправо.
  • Збій швидких механізмів навколо кодового шляху, який завантажує / перезавантажує значення з бази даних. (Якщо відповідна дія відсутня в програмі І вона не позначена ігноруванням, не робіть оновлення).

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


4

Зберігання їх в обох місцях (у таблиці та в ENUM) не так вже й погано. Міркування такі:

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

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

Одне, щойно визначено ENUM, не змінюйте його значення. Наприклад, якщо у вас були:

  • Apple
  • Виноград

НЕ перейменуйте виноград на виноград. Просто додайте новий ENUM.

  • Apple
  • Виноград
  • Виноград

Якщо вам потрібно мігрувати дані, застосуйте оновлення, щоб перемістити весь виноград до винограду.


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

1

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

Зважаючи на це, оцінка (ваші ifумови) не обов'язково повинна бути зосереджена на тому, як ви її обійшли. Натомість зверніть увагу на спосіб розповсюдження змін, які спричинили б проблему "не синхронізації".

Строковий підхід

Якщо вам потрібно використовувати рядки, чому б не відкрити функціональність зміни списку через інтерфейс користувача? Конструкція системи так , що при переході Grapeдо Grapes, наприклад, оновити всі записи в даний час заслання Grape.

Ідентифікаційний підхід

Я завжди вважаю за краще посилатись на ідентифікатор, незважаючи на компроміс з деякою читабельністю. The list may be added toзнову може бути те, про що ви б отримали сповіщення, якщо ви відкрили таку функцію інтерфейсу користувача. Якщо ви переймаєтесь тим, як упорядкувати елементи, що змінюють ідентифікатор, знову поширіть таку зміну на всі залежні записи. Аналогічно вище. Інший варіант (дотримуючись правильної норми нормалізації, - матиме свій стовпчик enum / id - і посилається на більш детальну FruitDetailтаблицю, у якій стовпчик "Замовлення", який ви можете шукати).

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


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

1
@ Clockwork-Muse - щоб уникнути якої проблеми? Це не має сенсу.
JᴀʏMᴇᴇ

Я досить часто використовую підхід до ідентифікації, але ідентифікатор заблокований і не може змінитися. Зрозуміло, що додається рядок може, тому що люди часто люблять перейменовувати речі "вантажівка" стає "вантажівкою" тощо, тоді як сама річ (представлена ​​ідентифікатором) не змінюється.
Брайан Кноблаух

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

Не повинно бути автоматично збільшення? У цьому випадку цього не повинно бути, особливо якщо це ціле значення переліку enum, яке ми використовуємо.
JᴀʏMᴇᴇ

0

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

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

Іноді буває і в інший спосіб. На клієнтській стороні додається нове значення перерахунку, але оновлення БД не може відбутися до тих пір, поки DBA не може застосувати зміни.


Так, ви описали проблему. Яке ваше рішення?
Захисник один

1
@Protectorone Ви припускаєте , там є куля розчин срібла , який є помилковим припущенням , в моєму досвіді. Найкраще, на що можна сподіватися, - це те, що деякий суб’єкт господарювання володіє проблемним доменом, тож принаймні ви можете бачити, яка сторона відстає - на стороні клієнта чи на базі даних. Банківські послуги та фінанси, як правило, дуже ефективні в цьому плані, тому що роздрібний сектор помітно менше ...
Роббі Ді

0

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

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

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


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

Гаразд, мені потрібен кращий термін "для статичного пошуку", що я маю на увазі контекст, який ви описуєте :) Ключове слово "статичне" - це значення, які не змінюють питання, додавання нових значень та зміна "мітки" ( але не намір) для існуючих значень.
Мерф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.