Odwoływanie się do wartości bazy danych w logice biznesowej

43

Wydaje mi się, że to kolejne pytanie o kodowanie na stałe i najlepsze praktyki. Powiedzmy, że mam listę wartości, powiedzmy owoców, przechowywanych w bazie danych (musi znajdować się w bazie danych, ponieważ tabela jest używana do innych celów, takich jak raporty SSRS), z identyfikatorem:

1 Apple 
2 Banana 
3 Grapes

Mogę przedstawić je użytkownikowi, on wybiera jeden, zostanie on zapisany w jego profilu jako FavouriteFruit, a identyfikator przechowywany w jego rekordzie w bazie danych.

Jeśli chodzi o reguły biznesowe / logikę domeny, jakie są zalecenia dotyczące przypisywania logiki do określonych wartości. Powiedz, jeśli użytkownik wybrał opcję Winogrona, chcę wykonać jakieś dodatkowe zadanie, jaki jest najlepszy sposób odniesienia wartości Winogrona:

// 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)

albo coś innego?

Ponieważ oczywiście aplikacja FavouriteFruit będzie używana w całej aplikacji, lista może być dodawana lub edytowana.

Ktoś może zdecydować, że chce zmienić nazwę „Winogrona” na „Winogrono”, co oczywiście złamałoby opcję napisanego na stałe.

Zaszyfrowany identyfikator nie jest do końca jasny, jak pokazano, można po prostu dodać komentarz, aby szybko zidentyfikować, który to element.

Opcja wyliczania polega na duplikowaniu danych z bazy danych, co wydaje się nieprawidłowe, ponieważ może się nie zsynchronizować.

W każdym razie z góry dziękujemy za wszelkie uwagi lub sugestie.

Kate
źródło
1
Dziękujemy wszystkim: sugestie i ogólne porady są naprawdę pomocne. @RemcoGerlich Twój pomysł, aby oddzielić obawy związane z ciągiem znaków używanym do wyświetlania, a osobny jako kod odnośnika dla bardziej czytelnego kodu jest bardzo dobry.
Kate,
1
Zamierzam dać @Mike Nakis pomysł na preinstalowane obiekty, ponieważ wydaje się to najlepsze z obu światów.
Kate,
1
Proponuję odmianę pierwszego rozwiązania. Poproś, aby tabela zawierała trzecią kolumnę dotyczącą tego, jak będzie przetwarzana, i użyj tego pola, aby określić, który kod ma zostać wykonany. Nie jest polem wyświetlania i może być dzielone między wiele owoców.
Kickstart,
1
Opcja wyliczania polega na duplikowaniu danych z bazy danych, co wydaje się nieprawidłowe, ponieważ może się nie zsynchronizować. Właściwie to mi się podoba. To jest jak podwójna księgowość. Jeśli obie strony księgi nie równoważą się, będziesz wiedział, że coś jest nie tak. Sprawia, że ​​zmiana rzeczy jest bardziej celowa.
radarbob,
1
Hmmm ... Jeśli istnieje relacja ID 1: 1 do łańcucha, jest to zbędne, a posiadanie obu nie ma sensu. Ciąg może służyć zarówno jako klucz DB, jak i liczba całkowita. MyApplication.Grape.IDto jąkanie, że tak powiem. „Apple” nie jest „Red_Apple”, podobnie jak identyfikator 3, wynosi również 4. Zatem możliwość zmiany nazwy „Apple” na „Red_Apple” nie ma większego sensu niż deklarowanie, że 3 to 4 (a może nawet 3). Celem wyliczenia jest wyodrębnienie jego numerycznego DNA. Może więc nadszedł czas, aby naprawdę oddzielić dowolne relacyjne klucze DB, które dosłownie nie mają znaczenia w modelach biznesowych.
radarbob

Odpowiedzi:

31

Za wszelką cenę unikaj ciągów i magicznych stałych. Są całkowicie wykluczone, nie należy ich nawet uważać za opcje. Wydaje się, że pozostawia Ci tylko jedną realną opcję: identyfikatory, czyli znaki wyliczeniowe. Jest jednak jeszcze jedna opcja, która moim zdaniem jest najlepsza. Nazwijmy tę opcję „Wstępnie załadowanymi obiektami”. Dzięki wstępnie załadowanym obiektom możesz wykonać następujące czynności:

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

To, co się właśnie wydarzyło, polega na tym, że oczywiście załadowałem cały wiersz Grapedo pamięci, więc mam jego identyfikator gotowy do użycia w porównaniach. Jeśli używasz mapowania obiektowo-relacyjnego (ORM), wygląda to jeszcze lepiej:

if( user.FavouriteFruit == MyApplication.Grape )

(Dlatego nazywam to „Wstępnie załadowanymi obiektami”).

Dlatego podczas uruchamiania ładuję wszystkie moje tabele „wyliczania” (małe tabele, takie jak dni tygodnia, miesiące roku, płcie itp.) Do głównej klasy domeny aplikacji. Ładuję je według nazwy, ponieważ oczywiście MyApplication.Grapemuszę otrzymać wiersz o nazwie „Winogrono” i zapewniam, że każdy z nich został znaleziony. Jeśli nie, mamy gwarantowany błąd w czasie wykonywania podczas uruchamiania, który jest najmniej złośliwy ze wszystkich błędów w czasie wykonywania.

Mike Nakis
źródło
17
Nie zgadzam się z odpowiedzią, ale uważam, że imperatyw „Unikaj strun i magicznych stałych za wszelką cenę” nie zgadza się z resztą odpowiedzi, co tak naprawdę wymaga , abyś miał przynajmniej jedno miejsce, w którym używane są magiczne stałe lub ciągi w wypełnianiu „wstępnie załadowanych obiektów”. Myślę, że jest to godne uwagi, ponieważ istnieją sposoby na całkowite uniknięcie „łańcuchów i magicznych stałych”, chociaż zwykle jest to bardziej zaciemniające niż warte ...
svidgen
2
@svidgen, czy nie zgadzasz się, że istnieje zasadnicza różnica między rozproszeniem wiązania według nazwy w całym miejscu a wiązaniem według nazwy tylko raz, aby załadować zawartość rekordu o tej samej nazwie, a robienie tego tylko podczas uruchamiania, gdzie błędy czasu wykonywania są prawie tak samo łagodne jak błędy kompilacji? W każdym razie sposoby unikania nawet najmniejszego wiązania z nazwy są zawsze interesujące, pomimo wspomnianego zaciemnienia, więc byłbym ciekawy, co masz na myśli.
Mike Nakis,
Och, całkowicie się zgadzam. Biorąc pod uwagę charakter PO, sugerowałbym jedynie, że na tę odpowiedź można by skorzystać ze zmiany „za wszelką cenę” na „zawsze, gdy jest to możliwe i wykonalne” lub coś podobnego. ... Gdybym miał więcej czasu, tylko ze względu na kompletność, napisałbym odpowiedź, która dotyczy pewnego rodzaju nonsensów z metaprogramowaniem ... ale nie tego prawdopodobnie potrzebuje OP (lub ktokolwiek w większości przypadków) . Ale rozwiązanie do metaprogammingu bardziej pasowałoby do pierwszego stwierdzenia w niezmienionej formie.
svidgen,
1
@ user469104 różnica polega na tym, że identyfikatory mogą się zmieniać, a aplikacja nadal poprawnie ładuje wszystkie wiersze i poprawnie wykonuje wszystkie porównania. Ponadto możesz dowolnie zmieniać kod i zmieniać nazwy wierszy w dowolny sposób, a jedynym miejscem, w którym musisz szukać rzeczy do naprawienia, jest uruchomienie aplikacji i jest to bardzo oczywiste: Grape = fetchRow( Fruit.class, NameColumn, "Grape" ); A jeśli zrób coś niepoprawnie, AssertionErrorda ci znać.
Mike Nakis,
1
@grahamparks nie był niczym więcej niż enummagicznym ciągiem. Chodzi o to, aby skupić wszystkie wiązania według nazwy w jednym miejscu , sprawdzić je podczas uruchamiania i zapewnić bezpieczeństwo typu .
Mike Nakis,
7

Sprawdzanie ciągu znaków jest najbardziej czytelne, ale spełnia podwójną funkcję: jest używane zarówno jako identyfikator, jak i opis (który może się zmienić z niepowiązanych powodów).

Zwykle dzielę oba obowiązki na osobne pola:

id  code    description
 1  grape   Grapes
 2  apple   Apple

Gdzie opis może się zmienić (ale nie „Winogrona” na „Banan”), ale kod nigdy nie może się zmienić.

Chociaż dzieje się tak głównie dlatego, że nasze identyfikatory są prawie zawsze generowane automatycznie, a zatem nie są dobrze dopasowane. Jeśli możesz swobodnie wybierać identyfikatory, być może możesz zagwarantować, że są one zawsze prawidłowe i używają ich.

Jak często ktoś tak naprawdę edytuje „Winogrona” do „Winogrona”? Może nic z tego nie jest konieczne.

RemcoGerlich
źródło
8
Nie sądzę, aby jeszcze więcej redundancji stanowiło odpowiedź ...
Robbie Dee
4
Rozważyłem również tę opcję i wypróbowałem ją, ale tak właśnie się stało: w pewnym momencie „jabłko” musiało zostać zróżnicowane na „green_apple” i „red_apple”. Ale ponieważ „jabłko” było już używane w niezliczonej liczbie miejsc w kodzie, nie mogłem zmienić jego nazwy, więc musiałem mieć „jabłko” i „zielony_apple”. W rezultacie Sheldon we mnie uniemożliwił mi spanie przez kilka nocy, dopóki tam nie wszedłem i przerobił wszystko na „Wstępnie załadowane obiekty”. (patrz moja odpowiedź.)
Mike Nakis,
1
Zdecydowanie podoba mi się Twoje wstępnie załadowane obiekty, ale jeśli twoje „jabłko” jest zróżnicowane, czy nie musisz i tak wszystko przewijać, jakąkolwiek metodę wybierzesz?
RemcoGerlich,
Możesz nawet mieć osobną tabelę dla nazwy opisu, w celu wsparcia internacjonalizacji.
Erik Eidt,
1
@ MikeNakis i Refaktoryzacja to w zasadzie wyszukiwanie i zamiana w całej bazie kodu, zastępując Fruit.Apple przez Fruit.GreenApple. Gdybym użył wartości Ciągu zakodowanego na stałe, zrobiłbym Search & Replace na całej bazie kodu, aby zamienić „jabłko” na „green_apple”, co jest mniej więcej tym samym. - Refaktoryzacja jest po prostu lepsza, ponieważ IDE dokonuje wymiany.
Falco,
4

Oczekuje się tutaj, że logika programowania będzie automatycznie dostosowywana do zmieniających się danych. Proste opcje statyczne, takie jak Enum, nie działają tutaj, ponieważ nie można w sposób wykonalny dodać dodatkowych wyliczeń w środowisku wykonawczym.

Kilka wzorów, które widziałem:

  • Wyliczenia + domyślnie chronią przed zupełnie nowym wpisem do bazy danych, który rujnuje dzień twojego programu.
  • Kodowanie działań do wykonania (logika biz) w samej bazie danych. W wielu przypadkach jest to bardzo możliwe, ponieważ wiele logiki jest ponownie wykorzystywanych. Implementacja logiki powinna odbywać się w programie.
  • Dodatkowe atrybuty / kolumny w bazie danych, aby oznaczyć nową wartość jako „do zignorowania” w programie, dopóki program nie zostanie poprawnie wdrożony.
  • Awarie szybkich mechanizmów wokół ścieżki kodu, która ładuje / przeładowuje wartości z bazy danych. (Jeśli odpowiednie działanie nie znajduje się w programie ORAZ nie jest zaznaczone do zignorowania, nie podejmuj odświeżania).

Zasadniczo podoba mi się to, że dane są kompletne, jeśli chodzi o odnoszące się do działań, które implikują - nawet jeśli same działania mogą być realizowane gdzie indziej. Każdy kod określający działania niezależne od danych właśnie podzielił twoją reprezentację danych, która najprawdopodobniej będzie się rozbierać i prowadzić do błędów.

Subu Sankara Subramanian
źródło
4

Przechowywanie ich w obu miejscach (w tabeli i w ENUM) nie jest takie złe. Rozumowanie jest następujące:

Przechowując je w tabeli bazy danych, możemy wymusić integralność referencyjną w bazie danych za pomocą kluczy obcych. Kiedy więc kojarzysz osobę lub jakąkolwiek istotę z owocem, w tabeli bazy danych jest tylko owoc.

Przechowywanie ich jako ENUM ma również sens, ponieważ możemy pisać kod bez magicznych ciągów, dzięki czemu kod jest bardziej czytelny. Tak, muszą być zsynchronizowane, ale tak naprawdę, jak trudno byłoby dodać wiersz do ENUM i nową instrukcję insert do bazy danych.

Po zdefiniowaniu ENUM nie zmieniaj jego wartości. Na przykład, jeśli miałeś:

  • jabłko
  • Winogrono

NIE zmieniaj nazwy winogron na winogrona. Po prostu dodaj nowy ENUM.

  • jabłko
  • Winogrono
  • Winogrona

Jeśli musisz przeprowadzić migrację danych, zastosuj aktualizację, aby przenieść wszystkie winogrona do winogron.

Jon Raynor
źródło
Jako kolejny krok pracowałem w sklepach, w których wartości metadanych mają w tabeli flagę usuwania, wskazującą, że nie należy ich używać (albo były przestarzałe, albo istnieje nowsza wersja).
Robbie Dee,
1

Masz rację, zadając to pytanie, właściwie to miłe pytanie, gdy próbujesz się bronić przed oceną niedokładnych warunków.

To powiedziawszy, ocena (twoje ifwarunki) niekoniecznie musi koncentrować się na tym, jak sobie z tym poradzić. Zamiast tego zwróć uwagę na sposób propagowania zmian, które spowodowałyby problem z brakiem synchronizacji.

Podejście strunowe

Jeśli musisz używać ciągów, dlaczego nie udostępnić funkcji zmiany listy za pomocą interfejsu użytkownika? Zaprojektować system tak, że po zmianie Grapena Grapes, na przykład, zaktualizować wszystkie rekordy obecnie przedstawieniu Grape.

Podejście ID

Zawsze wolałbym odwoływać się do identyfikatora, pomimo kompromisu w zakresie jego czytelności. The list may be added tomoże być coś, o czym zostaniesz powiadomiony, jeśli ujawnisz taką funkcję interfejsu użytkownika. Jeśli martwisz się zmianą kolejności elementów zmieniających identyfikator, ponownie propaguj taką zmianę do wszystkich zależnych rekordów. Podobnie jak powyżej. Inną opcją (zgodnie z właściwą konwencją normalizacyjną byłoby posiadanie kolumny wyliczeniowej / id - i odwołanie się do bardziej szczegółowej FruitDetailtabeli, która zawiera kolumnę „Zamówienie”, którą można wyszukać).

Tak czy inaczej, możesz zobaczyć, proponuję kontrolować zmianę lub aktualizację listy. To, czy robisz to za pomocą ORM, czy innego dostępu do danych, zależy od specyfiki Twojej technologii. To, co zasadniczo robisz, wymaga od ludzi, aby odeszli od DB dla takich zmian - co moim zdaniem jest w porządku. Większość głównych CRM będzie spełniać te same wymagania.

JᴀʏMᴇᴇ
źródło
1
W bazie danych zapisywany jest numeryczny identyfikator rekordów potomnych, aby uniknąć tego problemu. To pytanie dotyczy sposobu interfejsu z językiem programowania.
Clockwork-Muse,
1
@ Clockwork-Muse - aby uniknąć problemu? To nie ma sensu.
JᴀʏMᴇᴇ
Często używam podejścia do ID, ale ID jest zablokowane i nie można go zmienić. Dołączony ciąg znaków może oczywiście, ponieważ ludzie często lubią zmieniać nazwy rzeczy „ciężarówka” staje się „ciężarówka” itp., Podczas gdy sama rzecz (reprezentowana przez ID) nie zmienia się.
Brian Knoblauch,
Jeśli postępujesz zgodnie z podejściem opartym na ID, jak radzisz sobie z bazami danych programowania a produkcyjnymi? W przypadku automatycznie zwiększanych identyfikatorów dodawanie elementów do obu baz danych w różnej kolejności spowoduje uzyskanie różnych identyfikatorów.
Protektor jeden
Nie musi to być jednak automatyczny przyrost? Nie powinno tak być w tym przypadku, szczególnie jeśli jest to używana przez nas wartość całkowita wyliczenia.
JᴀʏMᴇᴇ
0

Bardzo częsty problem. Podczas gdy powielanie po stronie klienta danych może wydawać się naruszać zasady DRY , jest tak naprawdę z powodu różnicy w paradygmacie między warstwami.

Poza tym wyliczenie (lub cokolwiek innego) z bazą danych nie jest tak rzadkie. Być może wypchnąłeś inną wartość do tabeli metadanych w celu obsługi nowej funkcji raportów, która nie jest jeszcze używana w kodzie klienta.

Czasami dzieje się to również w drugą stronę. Nowa wartość wyliczana jest dodawana po stronie klienta, ale aktualizacja bazy danych nie może nastąpić, dopóki DBA nie zastosuje zmian.

Robbie Dee
źródło
Tak, opisałeś problem. Jakie jest twoje rozwiązanie
Protektor jeden
1
@Protectorone założyć tam jest srebrna kula rozwiązanie, które jest błędne założenie w moim doświadczeniu. Najlepsze, na co możesz mieć nadzieję, to to, że jakaś jednostka biznesowa jest właścicielem domeny problemowej, aby przynajmniej zobaczyć, która strona jest opóźniona - po stronie klienta lub po stronie bazy danych. Bankowość i finanse są zazwyczaj bardzo wydajne pod tym względem, a sektor detaliczny jest zauważalnie mniejszy ...
Robbie Dee
0

Zakładając, że mówimy o tym, co jest zasadniczo statycznym wyszukiwaniem, to trzecia opcja - wyliczenie - jest w zasadzie jedynym rozsądnym wyborem. To, co zrobiłbyś, gdyby baza danych nie była zaangażowana, więc ma to sens.

Następnie pojawia się pytanie o to, jak zsynchronizować wyliczenia i tabele statyczne / odnośniki w bazie danych, i niestety nie jest to problem, na który jeszcze mam pełną odpowiedź.

Z wyboru wykonuję całą konserwację schematu w kodzie i dlatego mogę zachować relację między kompilacją aplikacji a oczekiwaną wersją schematu, więc łatwo jest zsynchronizować wyszukiwanie i wyliczanie, ale należy pamiętać o tym, aby zrobić. Byłoby lepiej, gdyby był bardziej zautomatyzowany (a także automatyczny test integracji w celu upewnienia się, że wyliczenia i wyszukiwania pasują do siebie), ale nigdy tego nie wdrożyłem.

Murph
źródło
1
Nie wierzę, że są to tylko wyszukiwania statyczne, w przeciwnym razie można by je po prostu wyciągnąć z bazy danych i wykorzystać jako takie. Rozumiem, że problem polega na zastosowaniu logiki biznesowej w zależności od użytej wartości wyszukiwania. Ale poza tym tak - na ogół stosuje się do tego celu.
Robbie Dee,
Ok, potrzebuję lepszego terminu „do wyszukiwania statycznego”, o co mi chodziło, o którym mówiłem :) Kluczem jest „statyczny” - są to wartości, które nie zmieniają problemu, dodając nowe wartości i zmieniając „etykietę” ( ale nie zamiar) dla istniejących wartości.
Murph,