Ссылка на значения базы данных в бизнес-логике

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 будет использоваться во всем приложении, список может быть добавлен или отредактирован.

Кто-то может решить, что он хочет переименовать «Виноград» в «Виноград», и это, конечно, нарушит опцию жестко закодированных строк.

Жестко закодированный идентификатор не совсем понятен, хотя, как показано, вы можете просто добавить комментарий, чтобы быстро определить, какой это элемент.

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

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

Катя
источник
1
Спасибо всем: предложения и общие советы действительно полезны. @RemcoGerlich Ваша идея разделить проблемы строки, используемой для целей отображения, и отдельной строки в качестве кода поиска для более удобочитаемого кода, очень хороша.
Кейт
1
Я собираюсь дать @Mike Nakis идею о ваших предварительно загруженных объектах, так как это кажется лучшим из обоих миров.
Кейт
1
Я бы предложил вариант вашего первого решения. Ваша таблица содержит 3-й столбец для того, как она будет обрабатываться, и вы используете это поле, чтобы определить, какой код выполнять. Не поле отображения, и может быть разделен между несколькими фруктами.
Kickstart
1
Опция enum подразумевает дублирование данных из базы данных, что может показаться неправильным, поскольку оно может быть не синхронизировано. На самом деле мне это нравится. Это как двойная бухгалтерия. Если обе стороны бухгалтерской книги не сбалансированы, вы поймете, что что-то не так. Это делает изменение вещей более осознанным.
Радар Боб
1
Хммм ... Если есть отношение 1: 1 идентификатора к строке, то это избыточно, и иметь оба значения бессмысленно. Строка может служить как ключом БД, так и целым числом. MyApplication.Grape.IDзаикается, так сказать. «Яблоко» не является «Red_Apple», так как ID 3 также равен 4. Таким образом, потенциал для переименования «Apple» в «Red_Apple» не имеет больше смысла, чем объявление, что 3 равно 4 (а может быть, даже 3). Смысл перечисления - абстрагировать его числовую ДНК. Так что, возможно, пришло время по- настоящему отделить произвольные ключи реляционных БД, которые буквально не имеют смысла в бизнес-моделях.
радар Боб

Ответы:

31

Избегайте строк и магических констант любой ценой. Они полностью исключены, их даже не следует рассматривать как варианты. Похоже, что у вас остается только одна жизнеспособная опция: идентификаторы, то есть перечисления. Однако есть и еще один вариант, который, на мой взгляд, является лучшим. Давайте назовем эту опцию «Предварительно загруженные объекты». С предварительно загруженными объектами вы можете сделать следующее:

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

Что здесь произошло, так это то, что я, очевидно, загрузил весь ряд Grapeв память, поэтому у меня есть его идентификатор, готовый для использования в сравнениях. Если вы используете Object-Relational Mapping (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

Где описание может измениться (но не «Виноград» на «Банан»), но код не может быть изменен никогда.

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

Кроме того, как часто кто-то действительно редактирует "Виноград" в "Виноград"? Возможно, ничего из этого не нужно.

RemcoGerlich
источник
8
Я не думаю, что еще избыточность является ответом ...
Робби Ди
4
Я также рассмотрел эту опцию и попробовал ее, но это то, что в итоге произошло: в какой-то момент «яблоко» пришлось разделить на «green_apple» и «red_apple». Но поскольку «яблоко» уже использовалось во множестве мест в коде, я не мог его переименовать, поэтому мне пришлось иметь «яблоко» и «зеленый_апплет». И в результате Шелдон во мне не давал мне спать в течение нескольких ночей, пока я не вошел туда и не реорганизовал все в «Предварительно загруженные объекты». (см. мой ответ.)
Майк Накис
1
Мне определенно нравятся ваши предварительно загруженные объекты, но если ваше «яблоко» дифференцировано, разве вам не придется все равно проходить, какой бы метод вы ни выбрали?
RemcoGerlich
Вы могли бы даже иметь отдельную таблицу для имени описания в поддержку интернационализации.
Эрик Эйдт
1
@MikeNakis и Рефакторинг - это, по сути, поиск и замена по всей вашей кодовой базе, заменяя Fruit.Apple на Fruit.GreenApple. Если я использую значения жестко закодированных строк, я выполняю поиск и замену по всей базе кодов, чтобы заменить «яблоко» на «зеленый_апплет», что примерно так же. - Рефакторинг просто чувствует себя лучше, потому что в среде IDE выполняется замена.
Falco
4

Здесь вы ожидаете, что логика программирования будет автоматически адаптироваться к изменяющимся данным. Простые статические параметры, такие как Enum, здесь не работают, потому что вы не можете реально добавлять дополнительные перечисления во время выполнения.

Несколько моделей, которые я видел:

  • Enums + default для защиты от новой записи в базе данных, разрушающей день вашей программы.
  • Кодирование действий (логика бизнеса) в самой базе данных. Во многих случаях это очень возможно, потому что многие логики используются повторно. Реализация логики должна быть в программе.
  • Дополнительные атрибуты / столбцы в базе данных, чтобы пометить новое значение как «игнорируемое» в программе до правильного развертывания программы.
  • Отказ быстрых механизмов вокруг пути кода, который загружает / перезагружает значения из базы данных. (Если соответствующее действие отсутствует в программе И оно не помечено как игнорируемое, не выполняйте обновление).

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

Субу Шанкара Субраманиан
источник
4

Хранить их в обоих местах (в таблице и в ENUM) не так уж и плохо. Аргументация следующая:

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

Хранение их как ENUM также имеет смысл, потому что мы можем писать код без магических строк, и это делает код более читабельным. Да, они должны синхронизироваться, но на самом деле, как трудно будет добавить строку в ENUM и новый оператор вставки в базу данных.

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

  • яблоко
  • виноград

НЕ переименовывайте Виноград в Виноград. Просто добавьте новый ENUM.

  • яблоко
  • виноград
  • виноград

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

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

Вы правы, задавая этот вопрос, на самом деле это хороший вопрос, поскольку вы пытаетесь защититься от оценки неточных условий.

Тем не менее, оценка (ваши ifусловия) не обязательно должна быть в центре внимания, как вы обойти это. Вместо этого обратите внимание на то, как вы распространяете изменения, которые могут вызвать проблему «несинхронизации».

Струнный подход

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

Идентификационный подход

Я всегда предпочел бы ссылаться на идентификатор, несмотря на компромисс некоторой читабельности. The list may be added toснова может быть чем-то, о чем вы будете уведомлены, если обнаружите такую ​​функцию пользовательского интерфейса. Если вас беспокоит изменение порядка элементов, изменяющих идентификатор, распространите такое изменение еще раз на все зависимые записи. Как и выше. Другой вариант (следуя надлежащему соглашению о нормализации, будет иметь ваш столбец enum / id - и ссылаться на более подробную FruitDetailтаблицу, в которой есть столбец 'Order', который вы можете найти).

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

JᴀʏMᴇᴇ
источник
1
В базе данных числовой идентификатор хранится для дочерних записей, особенно во избежание этой проблемы. Этот вопрос о том, как взаимодействовать с языком программирования.
Заводная муза
1
@ Clockwork-Muse - чтобы избежать проблем? Это не имеет смысла.
JᴀʏMᴇᴇ
Я немного использую идентификационный подход, но идентификационный номер заблокирован и не может измениться. Прикрепленная строка, конечно, может, потому что люди часто любят переименовывать вещи в «грузовик», становится «грузовиком» и т. Д., В то время как сама вещь (представленная идентификатором) не меняется.
Брайан Кноблаух
Если вы придерживаетесь ID-подхода, как вы справляетесь с разработкой и производственными базами данных? При автоматическом увеличении идентификаторов добавление элементов в обе базы данных в разном порядке приведет к различным идентификаторам.
Один защитник
Не должен ли быть автоматический прирост, хотя? Так не должно быть, особенно если мы используем целочисленное значение перечисления.
JᴀʏMᴇᴇ
0

Очень распространенная проблема. Хотя дублирование клиентской части данных может показаться нарушением принципов DRY , это на самом деле связано с различием в парадигме между уровнями.

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

Иногда бывает и наоборот. Новое значение перечисления добавляется на стороне клиента, но обновление базы данных не может произойти до тех пор, пока администратор базы данных не сможет применить изменения.

Робби Ди
источник
Да, вы описали проблему. Каково ваше решение?
Один защитник
1
@Protectorone Вы предполагаете, что есть решение для серебряной пули, которое, по моему мнению, является ошибочным. Лучшее, на что вы можете надеяться, это то, что какой-то бизнес-объект владеет проблемным доменом, поэтому вы, по крайней мере, можете видеть, какая сторона отстает - на стороне клиента или на стороне базы данных. Банковское дело и финансы, как правило, очень эффективны в этом отношении, причем сектор розничной торговли заметно меньше ...
Робби Ди
0

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

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

По своему выбору я выполняю все операции по поддержке схемы в коде и, следовательно, могу поддерживать взаимосвязь между сборкой приложения и ожидаемой версией схемы, поэтому несложно поддерживать поиск и перечисление в синхронизации, но это то, что нужно запомнить делать. Было бы лучше, если бы он был более автоматизированным (а также автоматизированным интеграционным тестом, чтобы убедиться, что перечисления и совпадения поиска помогут), но я никогда не реализовывал это.

Murph
источник
1
Я не верю, что это просто статические поиски, иначе они могут быть просто извлечены из базы данных и использованы как есть. Проблема, насколько я понимаю, заключается в том, что бизнес-логика должна применяться в зависимости от используемого значения поиска. Но, кроме этого, да - перечисления обычно используются для этой цели.
Робби Ди
Хорошо, мне нужен лучший термин «для статического поиска», контекст, который вы описываете, это то, что я имел в виду :) Ключ «статический» - это значения, которые не меняют проблему, добавляя новые значения и меняя «метку» ( но не намерение) для существующих значений.
Мерф