Członek: Używaj unikalnych identyfikatorów vs obiektu domeny

10

Po kilku przydatnych odpowiedziach na pytanie, czy powinienem użyć obiektu domeny lub unikalnego identyfikatora jako parametru metody / funkcji tutaj Identyfikator vs. obiekt domeny jako parametru metody , mam podobne pytanie dotyczące: członkowie (w poprzednich dyskusjach nie udało się zakryj to). Jakie są zalety i wady używania unikalnych identyfikatorów jako członka vs. obiekt jako członek. Pytam w odniesieniu do mocno pisanych języków, takich jak Scala / C # / Java. Powinienem mieć (1)

User( id: Int, CurrentlyReadingBooksId: List[Int])
Book( id: Int, LoanedToId: Int )

lub (2), preferowane (1) Po przejściu: Czy powinniśmy definiować typy dla wszystkiego?

User( id: UserId, CurrentlyReadingBooksId: List[ BookId] )
Book( id: BookId, LoanedToId: UserId )

lub (3)

User( id: Int, CurrentlyReadingBooks: List[Book]) 
Book( id: Int, LoanedTo: User)

Chociaż nie mogę wymyślić korzyści z posiadania obiektu (3), jedną z korzyści posiadania identyfikatorów (2) i (1) jest to, że kiedy tworzę obiekt użytkownika z bazy danych, nie muszę tworzyć obiektu Book, który może z kolei zależeć od samego obiektu użytkownika, tworząc nieskończony łańcuch. Czy istnieje ogólne rozwiązanie tego problemu dla RDBMS i No-SQL (jeśli są różne)?

Na podstawie niektórych dotychczasowych odpowiedzi, przeformułowując moje pytanie: (przy użyciu identyfikatorów, które powinny znajdować się w opakowanych typach) 1) Czy zawsze używasz identyfikatorów? 2) Zawsze używasz obiektów? 3) Używać identyfikatorów, gdy istnieje ryzyko rekurencji podczas serializacji i deserializacji, ale używać obiektów w inny sposób? 4) Coś jeszcze?

EDYCJA: Jeśli odpowiesz, że Obiekty powinny być używane zawsze lub w niektórych przypadkach, upewnij się, że odpowiedziałeś na największe obawy, które opublikowali inni odpowiadający => Jak uzyskać dane z DB

0fnt
źródło
1
Dzięki za dobre pytanie, czekamy na odpowiedź z zainteresowaniem. Trochę szkoda, że ​​twoja nazwa użytkownika to „user18151”, ludzie z taką nazwą użytkownika są przez niektórych ignorowani :)
bjfletcher
@bjfletcher Dziękujemy. Sam miałem tę dokuczliwą percepcję, ale nigdy nie przyszło mi do głowy, dlaczego!
0

Odpowiedzi:

7

Obiekty Domeny jako identyfikatory tworzą pewne złożone / subtelne problemy:

Serializacja / deserializacja

Przechowywanie obiektów jako kluczy spowoduje, że serializacja wykresu obiektów będzie wyjątkowo skomplikowana. stackoverflowPodczas wykonywania naiwnej serializacji do JSON lub XML wystąpią błędy z powodu rekurencji. Będziesz wtedy musiał napisać niestandardowy serializator, który konwertuje rzeczywiste obiekty w celu użycia ich identyfikatorów zamiast serializacji instancji obiektu i utworzenia rekurencji.

Przekaż obiekty dla bezpieczeństwa typu, ale przechowuj tylko identyfikatory, wtedy możesz mieć metodę akcesora, która leniwie ładuje powiązaną jednostkę, gdy zostanie wywołana. Buforowanie drugiego poziomu zajmie się kolejnymi połączeniami.

Subtelne wycieki referencyjne:

Jeśli użyjesz obiektów domeny w konstruktorach takich, jakie masz, utworzysz odwołania cykliczne, które będą bardzo trudne do odzyskania pamięci dla obiektów nieużywanych aktywnie.

Idealna sytuacja:

Nieprzezroczyste identyfikatory vs int / long:

idPowinna być całkowicie nieprzezroczysta identyfikatorem, który niesie żadnej informacji o tym, co go identyfikuje. Ale powinien oferować pewną weryfikację, czy jest to prawidłowy identyfikator w swoim systemie.

Surowe typy łamią to:

int, longi Stringsą najczęściej używanymi typami surowymi dla identyfikatorów w systemie RDBMS. Istnieje długa historia praktycznych powodów, które sięgają dziesięcioleci i wszystkie są kompromisami, które albo pasują do oszczędzania, spacealbo do oszczędzania, timealbo do obu.

Identyfikatory sekwencyjne są najgorszymi przestępcami:

Podczas używania identyfikatora sekwencyjnego domyślnie pakowane są do niego informacje semantyczne. Co nie jest złe, dopóki nie zostanie użyte. Kiedy ludzie zaczynają pisać logikę biznesową, która sortuje lub filtruje semantyczną jakość identyfikatora, tworzą świat bólu dla przyszłych opiekunów.

String pola są problematyczne, ponieważ naiwni projektanci umieszczają informacje w treści, zwykle także w semantyce czasowej.

To sprawia, że ​​niemożliwe jest również stworzenie rozproszonego systemu danych, ponieważ nie12437379123 jest on unikalny na całym świecie. Szanse na to, że inny węzeł w systemie rozproszonym utworzy rekord o tej samej liczbie, są prawie całkowicie zagwarantowane, jeśli dostaniesz wystarczającą ilość danych w systemie.

Potem zaczynają się wokół niego hacki i cała ta rzecz przekształca się w kupę parującego bałaganu.

Ignorowanie ogromnych systemów rozproszonych ( klastrów ) staje się kompletnym koszmarem, gdy zaczniesz próbować udostępniać dane również innym systemom. Zwłaszcza, gdy drugi system nie jest pod twoją kontrolą.

Ostatecznie masz ten sam problem, jak uczynić swój identyfikator globalnym unikalnym.

UUID został utworzony i ustandaryzowany z następującego powodu:

UUIDmogą wystąpić wszystkie wyżej wymienione problemy, w zależności od tego, którego Versionużywasz.

Version 1używa adresu MAC i czasu, aby utworzyć unikalny identyfikator. Jest to złe, ponieważ przenosi semantyczne informacje o lokalizacji i czasie. Sam w sobie nie stanowi to problemu, kiedy naiwni programiści zaczynają polegać na tych informacjach w logice biznesowej. To powoduje także wyciek informacji, które można wykorzystać w próbach włamania.

Version 2wykorzystuje użytkowników UIDlub GIDdomian UIDlub GUImiejsce z tego czasu Version 1jest tak samo złe jak Version 1wyciek danych i ryzykuje, że informacje te zostaną wykorzystane w logice biznesowej.

Version 3jest podobny, ale zastępuje adres MAC i czas MD5hashem o pewnej tablicy byte[]z czegoś, co zdecydowanie ma znaczenie semantyczne. Nie ma wycieków danych, o które byte[]można się martwić, nie można ich odzyskać UUID. Daje to dobry sposób na deterministyczne tworzenie UUIDformularzy instancji i pewnego rodzaju klucza zewnętrznego .

Version 4 opiera się tylko na liczbach losowych, co jest dobrym rozwiązaniem, nie zawiera absolutnie żadnych informacji semantycznych, ale nie jest deterministycznie odtwarzalne.

Version 5jest jak, Version 4ale używa sha1zamiast md5.

Klucze domeny i klucze danych transakcyjnych

Moje preferencje dotyczące identyfikatorów obiektów domeny to używanie Version 5lub, Version 3jeśli z Version 5jakiegoś powodu technicznego jest to ograniczone .

Version 3 doskonale nadaje się do danych transakcyjnych, które mogą być rozłożone na wiele komputerów.

Jeśli nie jesteś ograniczony przestrzenią, użyj UUID:

Gwarantujemy unikalność, zrzucanie danych z jednej bazy danych i ponowne ładowanie do innej, nigdy nie musiałeś się martwić o zduplikowane identyfikatory, które faktycznie odnoszą się do różnych danych domeny.

Version 3,4,5 są całkowicie nieprzejrzyste i właśnie takie powinny być.

Możesz mieć jedną kolumnę jako klucz podstawowy za pomocą, UUIDa następnie możesz mieć złożone unikalne indeksy dla tego, co byłoby naturalnym złożonym kluczem podstawowym.

Pamięć masowa również nie musi CHAR(36). Możesz przechowywać UUIDw natywnym polu bajt / bit / liczba dla danej bazy danych, o ile jest ona nadal indeksowana.

Dziedzictwo

Jeśli masz typy RAW i nie możesz ich zmienić, nadal możesz wyodrębnić je w kodzie.

Używanie Version 3/5z UUIDwas może przejść w Class.getName()+ String.valueOf(int)postaci byte[]i mają nieprzezroczyste klucza odniesienia, który jest recreatable i deterministyczny.


źródło
Jest mi bardzo przykro, jeśli nie wyraziłem się jasno w swoim pytaniu i czuję się tym gorzej (lub faktycznie dobrze), ponieważ jest to świetna i przemyślana odpowiedź, a ty wyraźnie spędziłeś na niej czas. Niestety, nie pasuje do mojego pytania, może zasługuje na własne pytanie? „O czym należy pamiętać, tworząc pole identyfikatora dla mojego obiektu domeny”?
0
Dodałem wyraźne wyjaśnienie.
Mam to teraz. Dzięki za poświęcenie czasu na odpowiedź.
0
1
A tak przy okazji, generacyjne śmieciarki AFAIK (które według mnie są obecnie dominującym systemem GC) nie powinny mieć zbyt dużych trudności w okólnych referencjach GC.
0
1
czy C-> A -> B -> Ai Bumieszcza się w Collectionwtedy Ai wszystkie jego dzieci są jeszcze osiągalne, te rzeczy nie są całkowicie oczywiste i może prowadzić do subtelnych przecieków . GCjest najmniejszym z problemów, serializacja i deserializacja wykresu jest koszmarem złożoności.
2

Tak, korzyści są w obu przypadkach, a także kompromis.

List<int>:

  • Oszczędzaj pamięć
  • Szybsza inicjalizacja typu User
  • Jeśli dane pochodzą z relacyjnej bazy danych (SQL), nie musisz uzyskiwać dostępu do dwóch tabel, aby uzyskać użytkowników, tylko Userstabelę

List<Book>:

  • Dostęp do książki jest szybszy od użytkownika, książka została wstępnie załadowana do pamięci. Jest to miłe, jeśli możesz sobie pozwolić na dłuższy rozruch, aby uzyskać szybsze kolejne operacje.
  • Jeśli Twoje dane pochodzą z bazy danych magazynu dokumentów, takiej jak HBase lub Cassandra, wówczas wartości przeczytanych książek są prawdopodobnie zapisane w rekordzie użytkownika, więc możesz łatwo zdobyć książki „podczas odwiedzania użytkownika”.

Jeśli nie masz problemów z pamięcią lub procesorem, z którymi bym poszedł List<Book>, kod korzystający z Userinstancji będzie czystszy.

Kompromis:

Podczas korzystania z Linq2SQL, kod wygenerowany dla encji Użytkownik będzie miał EntitySet<Book>leniwy ładowany podczas uzyskiwania do niego dostępu. To powinno utrzymywać twój kod w czystości, a instancja użytkownika mała (pod względem wielkości pamięci).

ytoledano
źródło
Zakładając jakieś buforowanie, korzyść z wstępnego ładowania byłaby zerowa. Nie używałem Cassandra / HBase, więc nie mogę o nich mówić, ale Linq2SQL jest bardzo specyficznym przypadkiem (chociaż nie widzę, jak leniwe ładowanie zapobiegnie nieskończonemu łączeniu łańcuchów nawet w tym konkretnym przypadku i w ogólnym przypadku)
0
W przykładzie Linq2SQL naprawdę nie zyskujesz wydajności, a jedynie czystszy kod. W przypadku pozyskiwania jednostek od jednego do wielu ze sklepu z dokumentami, takiego jak Cassandra / HBase, znaczna większość czasu przetwarzania jest poświęcana na znajdowanie rekordu, więc równie dobrze można uzyskać wszystkie te jednostki, gdy tam jesteś (książki, w ten przykład).
ytoledano
Jesteś pewny? Nawet jeśli przechowuję książkę i użytkowników osobno znormalizowane? Dla mnie wygląda to na dodatkowe koszty związane z opóźnieniem sieci. W każdym razie, jak można ogólnie rozpatrywać sprawę RDBMS? (Zredagowałem pytanie, aby wyraźnie to zaznaczyć)
0
1

Krótka i prosta zasada:

Identyfikatory są używane w DTO .
Odwołania do obiektów są zwykle używane w obiektach warstwy Logika domeny / Logika biznesowa i UI.

To wspólna architektura w większych, wystarczająco przedsiębiorczych projektach. Będziesz miał twórców map, którzy tłumaczą tam iz powrotem na te dwa rodzaje obiektów.

herzmeister
źródło
Dziękujemy za zatrzymanie się i udzielenie odpowiedzi. Niestety, chociaż rozumiem to rozróżnienie dzięki linkowi do wiki, nigdy nie widziałem tego w praktyce (oczywiście, że nigdy nie pracowałem z dużymi długoterminowymi projektami). Czy miałbyś przykład, w którym ten sam obiekt był reprezentowany na dwa sposoby dla dwóch różnych celów?
0
oto aktualne pytanie dotyczące mapowania: stackoverflow.com/questions/9770041/dto-to-entity-mapping-tool - i są krytyczne artykuły takie jak to: rogeralsing.com/2013/12/01/…
herzmeister
Naprawdę pomocna, dzięki. Niestety nadal nie rozumiem, jak działałoby ładowanie danych za pomocą refrenów cyklicznych? np. jeśli użytkownik odwołuje się do książki, a książka odnosi się do tego samego użytkownika, jak utworzyłbyś ten obiekt?
0
Sprawdź wzorzec repozytorium . Będziesz miał BookRepositorya UserRepository. Zawsze będziesz dzwonić myRepository.GetById(...)lub podobnie, a repozytorium albo utworzy obiekt i załaduje jego wartości ze składnicy danych, albo pobierze go z pamięci podręcznej. Ponadto obiekty potomne są przeważnie ładowane z opóźnieniem, co również zapobiega konieczności radzenia sobie z bezpośrednimi odniesieniami kołowymi w czasie budowy.
herzmeister