W prawie wszystkich okolicznościach klucze podstawowe nie są częścią domeny biznesowej. Pewnie, możesz mieć pewne ważne obiekty z unikalnymi indeksami ( UserName
dla użytkowników lub OrderNumber
zamówień), ale w większości przypadków nie ma potrzeby jawnego identyfikowania obiektów domeny za pomocą pojedynczej wartości lub zestawu wartości, dla kogokolwiek, ale może użytkownik administracyjny. Nawet w tych wyjątkowych przypadkach, szczególnie jeśli używasz globalnych unikalnych identyfikatorów (GUID) , polubisz lub zechcesz użyć alternatywnego klucza zamiast ujawniać sam klucz podstawowy.
Tak więc, jeśli moje rozumienie projektowania opartego na domenie jest dokładne, klucze podstawowe nie muszą, a zatem nie powinny być odsłonięte, i dobra gra. Są brzydkie i dręczą mój styl. Ale jeśli zdecydujemy się nie włączać kluczy podstawowych do modelu domeny, będą miały konsekwencje:
- Naiwnie obiekty przesyłania danych (DTO), które pochodzą wyłącznie z kombinacji modeli domen, nie będą miały kluczy podstawowych
- Przychodzące DTO nie będą miały klucza podstawowego
Czy można więc powiedzieć, że jeśli naprawdę chcesz zachować czystość i wyeliminować klucze podstawowe w modelu domeny, powinieneś być przygotowany na to, że będziesz w stanie obsłużyć każde żądanie w zakresie unikalnych indeksów tego klucza podstawowego?
Innymi słowy, które z poniższych rozwiązań jest właściwe podejście do identyfikowania poszczególnych obiektów po usunięciu PK w modelach domenowych?
- Umiejętność rozpoznawania obiektów, z którymi musisz się zmierzyć, na podstawie innych atrybutów
- Odzyskiwanie klucza podstawowego z powrotem w DTO; tj. eliminując PK podczas mapowania z trwałości do domeny, a następnie rekombinując PK przy mapowaniu z domeny do DTO?
EDYCJA: Zróbmy to konkretnie.
Powiedzieć moim modelu domeny VoIPProvider
, która obejmuje obszary takie jak Name
, Description
, URL
, jak również odnośników podoba ProviderType
, PhysicalAddress
i Transactions
.
Powiedzmy teraz, że chcę zbudować usługę internetową, która pozwoli uprzywilejowanym użytkownikom zarządzać VoIPProvider
s.
Być może przyjazny dla użytkownika identyfikator jest w tym przypadku bezużyteczny; w końcu dostawcy VoIP są firmami, których nazwy są zwykle wyraźne w sensie komputerowym, a nawet wystarczająco wyraźne w sensie ludzkim ze względów biznesowych. Być może wystarczy powiedzieć, że unikat VoIPProvider
jest całkowicie zdeterminowany (Name, URL)
. Powiedzmy teraz, że potrzebuję metody PUT api/providers/voip
, aby uprzywilejowani użytkownicy mogli aktualizować VoIP
dostawców. Wysyłają VoIPProviderDTO
, który zawiera wiele, ale nie wszystkie pola z VoIPProvider
, w tym niektóre potencjalnie spłaszczające. Nie umiem jednak czytać w ich myślach i nadal muszą mi powiedzieć, o którym dostawcy mówimy.
Wygląda na to, że mam 2 (może 3) opcje:
- Uwzględnij klucz podstawowy lub klucz alternatywny w moim modelu domeny i wyślij go do DTO i odwrotnie
- Zidentyfikuj dostawcę, na którym nam zależy, za pomocą unikalnego indeksu, np
(Name, Url)
- Wprowadź jakiś rodzaj obiektu pośredniego, który zawsze może mapować między warstwą trwałości, domeną i DTO w sposób, który nie ujawnia szczegółów implementacji dotyczących warstwy trwałości - powiedzmy, wprowadzając tymczasowy identyfikator w pamięci podczas przechodzenia z domeny do DTO iz powrotem,
źródło
Odpowiedzi:
W ten sposób rozwiązujemy ten problem (od ponad 15 lat, kiedy nie wynaleziono nawet terminu „projektowanie oparte na domenie”):
Kiedy więc potrzebujesz klucza podstawowego do celów technicznych (takich jak mapowanie relacji do bazy danych), masz go dostępny, ale dopóki nie chcesz go „zobaczyć”, zmień poziom abstrakcji na „model ekspertów domeny „. I nie musisz utrzymywać „dwóch modeli” (jednego z PK i jednego bez); zamiast tego utrzymuj tylko model bez PK i użyj generatora kodu, aby utworzyć DDL dla swojej bazy danych, który automatycznie doda PK zgodnie z regułami mapowania.
Pamiętaj, że nie zabrania to dodawania żadnych „kluczy biznesowych”, takich jak dodatkowy „OrderNumber”, oprócz surogatu
OrderID
. Technicznie te klucze biznesowe stają się kluczami alternatywnymi podczas mapowania do bazy danych. Po prostu unikaj używania ich do tworzenia odniesień do innych tabel, zawsze preferuj używanie kluczy zastępczych, jeśli to możliwe, to znacznie ułatwi sprawę.Komentarz: użycie klucza zastępczego do identyfikacji rekordów nie jest operacją związaną z biznesem, jest operacją czysto techniczną. Aby to wyjaśnić, spójrz na swój przykład: dopóki nie zdefiniujesz dodatkowych unikalnych ograniczeń, możliwe byłoby posiadanie dwóch obiektów VoIPProvider o tej samej kombinacji (nazwa, adres URL), ale różnych VoIPProviderID.
źródło
Musisz być w stanie zidentyfikować wiele obiektów za pomocą jakiegoś unikalnego indeksu i nie jest to tym, co jest kluczem podstawowym (lub przynajmniej sugeruje, że jeden jest obecny).
Dostępne są unikalne indeksy, dzięki czemu można dodatkowo ograniczyć schemat DB, a nie jako hurtowy zamiennik PK. Jeśli nie ujawniasz PK, ponieważ są brzydkie, ale zamiast tego wystawiasz unikalny klucz ... tak naprawdę nie robisz nic innego. (Zakładam, że nie dostaniesz tutaj PK i kolumny tożsamości?)
źródło
Bez kluczy podstawowych w interfejsie nie ma łatwego sposobu, aby backend wiedział, co wysyłasz. Aby to naprawić, potrzebowałbyś mnóstwo dodatkowej pracy podczas analizowania danych, co obniżyłoby wydajność i prawdopodobnie zająłoby więcej czasu i byłoby bardziej brzydkie niż dołączanie klucza do każdego elementu.
Na przykład załóżmy, że chcę edytować wiadomość w aplikacji; skąd aplikacja miałaby wiedzieć, którą wiadomość chcę edytować bez dołączonego klucza podstawowego? Edycja obiektów odbywa się cały czas, a robienie tego bez kluczy jest prawie niemożliwe. Ale jeśli masz obiekty, które nie powinny być edytowane, pomiń klucz, jeśli uważasz, że to rozprasza, ale posiadanie kluczy podstawowych może tutaj poprawić wydajność.
źródło
MessageSender
,MessageRecipient
,TimeSent
- które powinny być unikalne.Powodem, dla którego używamy PK niezwiązanego z biznesem, jest zapewnienie, że nasz system ma łatwą i spójną metodę określania tego, czego chce użytkownik.
Widzę, że odpowiedziałeś komentarzem: MessageSender, MessageRecipient, TimeSent (dla wiadomości). W ten sposób możesz nadal mieć niejednoznaczność (na przykład z generowanymi przez system komunikatami wyzwalającymi coś, co często się zdarza). I jak zamierzasz tutaj zweryfikować MessageSender i MessageRecipient? Załóżmy, że zweryfikujesz je przy użyciu imienia, nazwiska, daty i daty urodzenia, w końcu trafisz na sytuację, w której urodzą się 2 osoby tego samego dnia o tym samym nazwisku. Nie wspominając już o tym, że spotka Cię wiadomość o nazwie
tacostacostacos-America-1980-Doc Brown-France-1965-23/5/2014-11:43:54.003UTC+200
. To potwór o imieniu i nadal nie masz gwarancji, że będziesz mieć tylko 1 z nich.powodem, dla którego używamy klucza podstawowego jest to, że WIEMY, że będzie unikalny przez cały okres użytkowania oprogramowania, bez względu na wprowadzane dane, i WIEMY, że będzie to przewidywalny format (co się stanie, jeśli powyższy klucz ma myślnik w nazwie użytkownika? cały twój system się psuje).
Nie musisz pokazywać swojego ID swojemu użytkownikowi. Możesz to ukryć (w razie potrzeby za pomocą adresu URL).
Innym powodem, dla którego PK jest tak przydatny, jest coś, co można wywnioskować z powyższego: PK sprawia, że nie trzeba zmuszać komputera do interpretacji kodu generowanego przez użytkownika. Jeśli chiński użytkownik użyje twojego kodu i wpisze kilka chińskich znaków, twój kod nagle nie musi być w stanie pracować z nimi wewnętrznie, ale może po prostu użyć Guida wygenerowanego przez system. Jeśli masz arabskiego użytkownika, który zaczyna pisać po arabsku, twój system nie musi sobie z tym poradzić wewnętrznie, ale może po prostu zignorować to, że tam jest.
Jak powiedzieli inni, przewodnik jest czymś, co można przechowywać wewnętrznie w ustalonym rozmiarze. Wiesz, z czym pracujesz i jest to coś, co może być powszechnie stosowane. Nie musisz tworzyć reguł projektowych dotyczących sposobu tworzenia określonego identyfikatora i zapisywania go. Jeśli twój system przyjmuje tylko pierwsze 10 liter imienia, nie widzi żadnej różnicy między Michaelem Guggenheimerem a Michaelem Gugsteinem, i myli je 2. Jeśli odetniesz go na dowolnej długości, możesz wpaść w zamieszanie. Jeśli ograniczysz wprowadzanie danych przez użytkownika, możesz napotkać problemy z ograniczeniami użytkownika.
Kiedy patrzę na istniejące systemy, takie jak Dynamics CRM, używają również klucza wewnętrznego (PK), aby użytkownik mógł wywołać pojedynczy rekord. Jeśli użytkownik ma zapytanie, które nie wiąże się z identyfikatorem, zwraca tablicę możliwych odpowiedzi i pozwala mu wybrać. Jeśli istnieje jakakolwiek szansa na dwuznaczność, dadzą wybór użytkownikowi.
Wreszcie, jest to także kwestia bezpieczeństwa poprzez niejasność. Jeśli nie znasz identyfikatora rekordu, jedyną opcją jest odgadnięcie. jeśli identyfikator jest łatwy do odgadnięcia (ponieważ informacje, z których jest wykonany, są publicznie dostępne), każdy może go zmienić. Możesz nawet nakłonić użytkownika do zmiany go za pomocą klasycznych metod CSRF lub XSS. Teraz, oczywiście, twoje zabezpieczenia powinny już były uwzględnione i zminimalizowane przed opublikowaniem wersji na żywo, ale powinieneś utrudnić potencjalne nadużycia.
źródło
Wydając identyfikator dla systemu zewnętrznego, należy podawać tylko identyfikatory URI lub klucz lub zestaw kluczy, które mają te same właściwości co identyfikator URI, zamiast ujawniać bezpośrednio klucz podstawowy bazy danych (od tego momentu będę się odnosił do zarówno URI, jak i klucz lub zestaw kluczy, które mają takie same właściwości jak URI jak tylko URI, innymi słowy poniższy URI niekoniecznie oznacza RFC 3986 URI).
Identyfikator URI może zawierać klucz podstawowy obiektu, ale nie musi składać się z kluczy alternatywnych. To naprawdę nie ma znaczenia. Ważne jest to, że tylko system, który generuje identyfikator URI, może rozdzielać lub łączyć identyfikator URI, aby zrozumieć, czym jest wskazany obiekt. Systemy zewnętrzne powinny zawsze używać identyfikatora URI jako nieprzezroczystego identyfikatora. Nie ma znaczenia, czy użytkownik może rozpoznać, że jedna część identyfikatora URI jest w rzeczywistości kluczem zastępczym bazy danych, czy składa się z kilku kluczy biznesowych zebranych razem, czy też jest to podstawa 64 tych wartości. Te są nieistotne. Liczy się to, że nie trzeba wymagać, aby system zewnętrzny rozumiał, co oznacza identyfikator, aby użyć identyfikatora. Systemy zewnętrzne nigdy nie powinny być zobowiązane do parsowania komponentów w obrębie identyfikatora lub łączenia identyfikatora z innymi identyfikatorami w celu odniesienia się do czegoś w twoim systemie.
Korzystanie z GUID spełnia niektóre z tych kryteriów, jednak identyfikacja taka jak GUID może być trudna do wyrejestrowania z powrotem do obiektu, nawet w systemie, więc klucze, które są nieprzejrzyste nawet dla twojego systemu, takie jak GUID, powinny być stosowane tylko wtedy, gdy klient analizuje URI / identyfikator stanowi zagrożenie dla bezpieczeństwa.
Wracając do przykładu VoIP, powiedz, że dostawca VoIP może być jednoznacznie określony przez (VoIPProviderID) lub (Nazwa, URL) lub (GUID). Gdy system zewnętrzny musi zaktualizować dostawcę VoIP, może po prostu przekazać PUT / provider / by-id / 1234 lub
PUT /provider/foo-voip/bar-domain.com
lubPUT /3F2504E0-4F89-41D3-9A0C-0305E82C3301
twój system zrozumie, że system zewnętrzny chce zaktualizować VoIPProvider. Te identyfikatory URI są generowane przez twój system i tylko twój system musi zrozumieć, że wszystkie one oznaczają to samo. System zewnętrzny powinien traktować wszystko, co znajduje się w URI, w zasadzie jakoPUT <whatever>
.Załóżmy, że masz dane różnych dostawców VoIP przechowywane w różnych tabelach z różnymi schematami (a zatem zupełnie inny zestaw kluczy identyfikuje każdego dostawcę VoIP na podstawie tabeli, w której są przechowywane). Gdy masz identyfikator URI, system zewnętrzny może uzyskać do niego jednolity dostęp, bez względu na to, jak Twój system identyfikuje konkretnego dostawcę VoIP. Dla systemu zewnętrznego jest to po prostu nieprzezroczysty wskaźnik.
Kiedy system używa identyfikatora URI do odwoływania się do obiektów w taki sposób, nie tracisz nic na temat sposobu implementacji systemu. Generujesz identyfikator URI, a klient po prostu przekazuje go z powrotem.
źródło
Będę musiał celować w to szalenie niedokładne i naiwne stwierdzenie:
Nazwy są okropne jak klucze, ponieważ często się zmieniają. Firma może mieć wiele nazw w ciągu swojego życia, firma może łączyć, dzielić, ponownie łączyć, tworzyć odrębną spółkę zależną do określonych celów podatkowych, która ma 0 pracowników, ale wszystkich klientów, którzy następnie zatrudniają pracowników z zupełnie innej spółki zależnej.
Następnie wchodzimy w fakt, że nazwy firm nie są nawet zdalnie unikalne, jak pokazuje przełomowy Apple vs. Apple .
Dobry obiektowy obiekt odwzorowujący lub frameworki powinien wyodrębnić klucze podstawowe i uczynić je niewidocznymi, ale one tam są i zazwyczaj będą jedynym sposobem na jednoznaczną identyfikację obiektu w bazie danych.
Dla porównania wolę, jak django sobie z tym radzi:
W ten sposób dane dostawcy klienta mogą być dostępne w kodzie za pomocą:
Chociaż klucze podstawowe / obce są niewidoczne, są tam i mogą być używane do uzyskiwania dostępu do przedmiotów, ale są abstrakcyjne.
źródło
Myślę, że często wciąż niepoprawnie patrzymy na ten problem z perspektywy DB: och, nie ma naturalnego klucza, więc musimy stworzyć klucz zastępczy. O nie, nie możemy odsłonić klucza zastępczego z powrotem do obiektów domeny, to jest nieszczelne itp.
Czasem jednak lepszym rozwiązaniem jest: jeśli obiekt biznesowy (domena) nie ma klucza naturalnego, być może należy go podać. Jest to podwójny problem z domeną biznesową: po pierwsze, rzeczy wymagają tożsamości, nawet przy braku baz danych. Po drugie, chociaż staramy się udawać, że wytrwałość jest jakimś abstrakcyjnym pomysłem niewidocznym dla domeny, rzeczywistość jest taka, że wytrwałość jest nadal koncepcją biznesową. Oczywiście istnieją problemy, w których wybrany klucz naturalny nie jest obsługiwany przez DB jako klucz podstawowy (np. GUID w niektórych systemach) - w takim przypadku trzeba będzie dodać klucz zastępczy.
W ten sposób kończysz w bardzo podobnym miejscu, np. Twój klient ma identyfikator liczby całkowitej, ale zamiast czuć się źle, ponieważ wyciekł z DB do domeny, czujesz się szczęśliwy, ponieważ firma zgodziła się, aby wszystkim klientom przypisano identyfikator ID i utrwalasz to w DB, jak musisz. Nadal możesz wprowadzić surogat, np. W celu wsparcia zmiany nazwy identyfikatora klienta.
Podejście to oznacza również, że jeśli obiekt domeny uderza w warstwę trwałości i nie ma identyfikatora, to prawdopodobnie jest to jakiś obiekt wartości, więc nie potrzebuje identyfikatora.
źródło