Mitglied: Verwenden Sie eindeutige IDs gegenüber dem Domänenobjekt

10

Nach ein paar nützlichen Antworten, ob ich hier ein Domänenobjekt oder eine eindeutige ID als Methoden- / Funktionsparameter verwenden soll. Kennung vs. Domänenobjekt als Methodenparameter , habe ich eine ähnliche Frage zu: Mitgliedern (die vorherige Fragendiskussion hat es nicht geschafft decken Sie dies ab). Was sind die Vor- und Nachteile der Verwendung eindeutiger IDs als Mitglied gegenüber Objekt als Mitglied? Ich frage in Bezug auf stark typisierte Sprachen wie Scala / C # / Java. Sollte ich haben (1)

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

oder (2), bevorzugt gegenüber (1) Nach dem Durchlaufen: Sollen wir Typen für alles definieren?

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

oder (3)

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

Während ich mir keine Vorteile für das Objekt (3) vorstellen kann, besteht ein Vorteil für die IDs (2) und (1) darin, dass ich beim Erstellen des Benutzerobjekts aus der Datenbank nicht das Buchobjekt erstellen muss, das kann wiederum vom Benutzerobjekt selbst abhängen und eine endlose Kette bilden. Gibt es eine generische Lösung für dieses Problem sowohl für RDBMS als auch für No-SQL (wenn sie unterschiedlich sind)?

Basierend auf einigen Antworten, die meine Frage bisher umformuliert haben: (unter Verwendung von IDs, die in verpackten Typen vorliegen sollen) 1) Immer IDs verwenden? 2) Immer Objekte verwenden? 3) Verwenden Sie IDs, wenn beim Serialisieren und Deserialisieren die Gefahr einer Rekursion besteht, aber verwenden Sie Objekte anderweitig? 4) Sonst noch etwas?

BEARBEITEN: Wenn Sie antworten, dass Objekte immer oder in einigen Fällen verwendet werden sollen, stellen Sie bitte sicher, dass Sie die größte Sorge beantworten, die andere Antwortende gestellt haben => So erhalten Sie Daten aus der Datenbank

0fnt
quelle
1
Vielen Dank für die gute Frage, freuen uns darauf, diese mit Interesse zu verfolgen. Ein bisschen schade, dass Ihr Benutzername "user18151" ist, Leute mit dieser Art von Benutzernamen werden von einigen ignoriert :)
bjfletcher
@bjfletcher Danke. Ich hatte diese quälende Wahrnehmung selbst, aber mir ist nie in den Sinn gekommen, warum!
0fnt

Antworten:

7

Domänenobjekte als IDs verursachen einige komplexe / subtile Probleme:

Serialisierung / Deserialisierung

Wenn Sie Objekte als Schlüssel speichern, wird die Serialisierung des Objektdiagramms äußerst kompliziert. Sie erhalten stackoverflowFehler , wenn aufgrund der Rekursion eine naive Serialisierung JSON oder XML zu tun. Sie müssen dann einen benutzerdefinierten Serializer schreiben, der die tatsächlichen Objekte konvertiert, um ihre IDs zu verwenden, anstatt die Objektinstanz zu serialisieren und die Rekursion zu erstellen.

Übergeben Sie Objekte zur Typensicherheit, speichern Sie jedoch nur IDs. Anschließend können Sie eine Zugriffsmethode verwenden, die die zugehörige Entität beim Aufruf verzögert lädt. Das Caching der zweiten Ebene kümmert sich um nachfolgende Anrufe.

Subtile Referenzlecks:

Wenn Sie Domänenobjekte in Konstruktoren verwenden, wie Sie sie dort haben, erstellen Sie Zirkelverweise, bei denen es sehr schwierig ist, Speicher für Objekte zurückzugewinnen, die nicht aktiv verwendet werden.

Ideale Situation:

Undurchsichtige IDs vs int / long:

Ein idsollte ein vollständig undurchsichtiger Bezeichner sein, der keine Informationen darüber enthält, was er identifiziert. Es sollte jedoch eine gewisse Bestätigung bieten, dass es sich um eine gültige Kennung in seinem System handelt.

Rohe Typen brechen dies:

int, longUnd Stringsind die am häufigsten rohen Typen für Bezeichner in RDBMS - System verwendet. Es gibt eine lange Geschichte praktischer Gründe, die Jahrzehnte zurückreichen, und alle sind Kompromisse, die entweder zum Sparen spaceoder zum Sparen timeoder zu beidem passen .

Sequentielle IDs sind die schlimmsten Straftäter:

Wenn Sie eine sequentielle ID verwenden, packen Sie standardmäßig zeitliche semantische Informationen in die ID. Welches ist nicht schlecht, bis es verwendet wird. Wenn Leute anfangen, Geschäftslogik zu schreiben, die die semantische Qualität der ID sortiert oder filtert, dann schaffen sie eine Welt voller Schmerzen für zukünftige Betreuer.

String Felder sind problematisch, weil naive Designer Informationen in den Inhalt packen, normalerweise auch zeitliche Semantik.

Diese machen es unmöglich, auch ein verteiltes Datensystem zu erstellen, da 12437379123es global nicht eindeutig ist. Die Wahrscheinlichkeit, dass ein anderer Knoten in einem verteilten System einen Datensatz mit derselben Nummer erstellt, ist so gut wie garantiert, wenn Sie genügend Daten in einem System erhalten.

Dann beginnen Hacks, es zu umgehen, und das Ganze verwandelt sich in einen Haufen dampfenden Chaos.

Wenn Sie große verteilte Systeme ( Cluster ) ignorieren , wird dies zu einem Albtraum, wenn Sie versuchen, die Daten auch mit anderen Systemen zu teilen. Besonders wenn das andere System nicht unter Ihrer Kontrolle ist.

Sie haben genau das gleiche Problem, wie Sie Ihre ID global eindeutig machen können.

UUID wurde aus einem Grund erstellt und standardisiert:

UUIDkann unter allen oben aufgeführten Problemen leiden, je nachdem, welche VersionSie verwenden.

Version 1Verwendet eine MAC-Adresse und Zeit, um eine eindeutige ID zu erstellen. Dies ist schlecht, da es semantische Informationen über Ort und Zeit enthält. Das ist an sich kein Problem, wenn naive Entwickler sich für die Geschäftslogik auf diese Informationen verlassen. Dadurch werden auch Informationen verloren, die bei Eindringversuchen ausgenutzt werden könnten.

Version 2Die Verwendung eines Benutzers UIDoder eines GIDDomians UIDoder GUIanstelle der Zeit Version 1davon ist genauso schlimm wie Version 1für Datenlecks und das Risiko, dass diese Informationen in der Geschäftslogik verwendet werden.

Version 3ist ähnlich, ersetzt jedoch die MAC-Adresse und die Zeit durch einen MD5Hash eines Arrays byte[]von etwas, das definitiv eine semantische Bedeutung hat. Es gibt keinen Datenverlust, über den Sie byte[]sich Sorgen machen müssen. Der Datenverlust kann nicht behoben werden UUID. Dies gibt Ihnen eine gute Möglichkeit, UUIDInstanzformulare und externe Schlüssel deterministisch zu erstellen .

Version 4 basiert nur auf Zufallszahlen, was eine gute Lösung ist, es enthält absolut keine semantischen Informationen, aber es ist nicht deterministisch wiederherstellbar.

Version 5ist genauso wie Version 4aber verwendet sha1statt md5.

Domänenschlüssel und Transaktionsdatenschlüssel

Ich bevorzuge Domänenobjekt-IDs, wenn ich sie aus technischen Gründen verwende Version 5oder Version 3wenn ich sie nicht verwenden darf Version 5.

Version 3 eignet sich hervorragend für Transaktionsdaten, die möglicherweise auf viele Computer verteilt sind.

Verwenden Sie eine UUID, es sei denn, Sie sind durch Speicherplatz eingeschränkt:

Sie sind garantiert eindeutig, geben Daten aus einer Datenbank aus und laden sie erneut in eine andere. Sie mussten sich nie um doppelte IDs sorgen, die tatsächlich auf unterschiedliche Domänendaten verweisen.

Version 3,4,5 sind völlig undurchsichtig und so sollten sie sein.

Sie können eine einzelne Spalte als Primärschlüssel mit einem UUIDund dann zusammengesetzte eindeutige Indizes für einen natürlichen zusammengesetzten Primärschlüssel haben.

Speicher muss auch nicht sein CHAR(36). Sie können das UUIDFeld in einem nativen Byte- / Bit- / Zahlenfeld für eine bestimmte Datenbank speichern, solange es noch indizierbar ist.

Erbe

Wenn Sie unformatierte Typen haben und diese nicht ändern können, können Sie sie dennoch in Ihrem Code abstrahieren.

Wenn Sie einen Version 3/5von UUIDIhnen verwenden, können Sie das Class.getName()+ String.valueOf(int)als a übergeben byte[]und einen undurchsichtigen Referenzschlüssel haben, der neu erstellt und deterministisch ist.


quelle
Es tut mir sehr leid, wenn ich in meiner Frage nicht klar war, und ich fühle mich umso schlechter (oder tatsächlich gut), weil dies eine so gute und gut durchdachte Antwort ist und Sie eindeutig Zeit damit verbracht haben. Leider passt es nicht zu meiner Frage, vielleicht verdient es eine eigene Frage? "Was muss ich beim Erstellen eines ID-Felds für mein Domain-Objekt beachten?"
0fnt
Ich habe eine explizite Erklärung hinzugefügt.
Hab es jetzt verstanden. Vielen Dank, dass Sie sich mit der Antwort beschäftigt haben.
0fnt
1
Übrigens sollten AFAIK-Müllsammler der Generation (von denen ich glaube, dass sie heutzutage das dominierende GC-System sind) keine allzu großen Schwierigkeiten haben, Zirkelverweise zu erstellen.
0fnt
1
Wenn C-> A -> B -> Aund Bin ein CollectionDann gesetzt wird Aund alle seine Kinder noch erreichbar sind, sind diese Dinge nicht ganz offensichtlich und können zu subtilen Lecks führen . GCist das geringste der Probleme, Serialisierung und Deserialisierung des Graphen ist ein Albtraum der Komplexität.
2

Ja, beide Wege haben Vorteile, und es gibt auch einen Kompromiss.

List<int>::

  • Speicher speichern
  • Schnellere Initialisierung des Typs User
  • Wenn Ihre Daten aus einer relationalen Datenbank (SQL) stammen, müssen Sie nicht auf zwei Tabellen zugreifen, um Benutzer zu erhalten, sondern nur auf die UsersTabelle

List<Book>::

  • Der Zugriff auf ein Buch ist für den Benutzer schneller. Das Buch wurde vorab in den Speicher geladen. Dies ist hilfreich, wenn Sie sich einen längeren Start leisten können, um schnellere nachfolgende Vorgänge zu erhalten.
  • Wenn Ihre Daten aus einer Dokumentenspeicherdatenbank wie HBase oder Cassandra stammen, sind die Werte der gelesenen Bücher wahrscheinlich im Benutzerdatensatz enthalten, sodass Sie die Bücher leicht "während Sie dort waren, um den Benutzer zu erhalten" hätten erhalten können.

Wenn Sie keine Speicher- oder CPU-Bedenken haben, würde List<Book>der Code, der die UserInstanzen verwendet, sauberer sein.

Kompromiss:

Bei Verwendung von Linq2SQL wird für den für die Entität Benutzer generierten Code ein Code EntitySet<Book>geladen, der beim Zugriff verzögert geladen wird. Dies sollte Ihren Code sauber halten und die Benutzerinstanz klein halten (Speicherbedarf).

Ytoledano
quelle
Unter der Annahme einer Art Caching wäre der Vorteil des Vorladens null. Ich habe Cassandra / HBase nicht verwendet, kann also nicht darüber sprechen, aber Linq2SQL ist ein sehr spezifischer Fall (obwohl ich nicht sehe, wie ein verzögertes Laden den Fall der unendlichen Verkettung selbst in diesem speziellen Fall und im allgemeinen Fall verhindert).
0fnt
Im Linq2SQL-Beispiel erhalten Sie wirklich keinen Leistungsvorteil, nur saubereren Code. Wenn Sie eins zu viele Entitäten aus einem Dokumentenspeicher wie Cassandra / HBase abrufen, wird der größte Teil der Verarbeitungszeit für das Auffinden des Datensatzes aufgewendet. Sie können also genauso gut alle vielen Entitäten abrufen, während Sie dort sind (die Bücher in) dieses Beispiel).
Ytoledano
Bist du sicher? Auch wenn ich Buch und Benutzer getrennt normalisiert speichere? Für mich sieht es so aus, als ob es nur zusätzliche Kosten für die Netzwerklatenz geben sollten. Wie geht man auf jeden Fall generisch mit dem RDBMS-Fall um? (Ich habe die Frage bearbeitet, um das klar zu erwähnen)
0fnt
1

Kurze und einfache Faustregel:

IDs werden in DTOs verwendet .
Objektreferenzen werden normalerweise in den Layerobjekten Domain Logic / Business Logic und UI verwendet.

Das ist die übliche Architektur in größeren, unternehmerisch genug Projekten. Sie haben Mapper, die diese beiden Arten von Objekten hin und her übersetzen.

herzmeister
quelle
Vielen Dank für Ihren Besuch und Ihre Antwort. Obwohl ich den Unterschied dank des Wiki-Links verstehe, habe ich dies leider noch nie in der Praxis gesehen (vorausgesetzt, ich habe noch nie mit großen Langzeitprojekten gearbeitet). Hätten Sie ein Beispiel, in dem dasselbe Objekt auf zwei Arten für zwei verschiedene Zwecke dargestellt wurde?
0fnt
Hier ist eine aktuelle Frage zum Mapping: stackoverflow.com/questions/9770041/dto-to-entity-mapping-tool - und es gibt kritische Artikel wie diesen: rogeralsing.com/2013/12/01/…
herzmeister
Wirklich hilfreich, danke. Leider verstehe ich immer noch nicht, wie das Laden von Daten mit zirkulären Referenzen funktionieren würde. Wenn ein Benutzer beispielsweise auf ein Buch verweist und das Buch auf denselben Benutzer verweist, wie würden Sie dieses Objekt erstellen?
0fnt
Schauen Sie sich das Repository-Muster an . Du wirst ein BookRepositoryund ein haben UserRepository. Sie rufen immer myRepository.GetById(...)oder ähnliches auf, und das Repository erstellt entweder das Objekt und lädt seine Werte aus einem Datenspeicher oder ruft es aus einem Cache ab. Außerdem werden untergeordnete Objekte meist faul geladen, wodurch verhindert wird, dass während der Erstellungszeit direkte Zirkelverweise verarbeitet werden müssen.
Herzmeister