In der Domain Driven Design ist eines der definierenden Merkmale einer Entity, dass sie eine Identität hat.
Problem:
Ich kann Entitäten bei der Instanzerstellung keine eindeutige Identität zuweisen. Diese Identität wird nur vom Repository bereitgestellt, sobald die Entität beibehalten wurde (dieser Wert wird von der zugrunde liegenden Datenbank bereitgestellt).
Ich kann an dieser Stelle nicht Guid
Werte verwenden. Die vorhandenen Daten werden mit int
Primärschlüsselwerten gespeichert und ich kann kein eindeutiges int bei Instanziierung generieren.
Meine Lösung:
Code (die abstrakte Basisklasse für alle Entitäten):
%Vor%Frage:
Ich kann Entitäten bei der Instanzerstellung keine eindeutige Identität zuweisen. Diese Identität wird nur vom Repository bereitgestellt, sobald die Entität beibehalten wurde (dieser Wert wird von der zugrunde liegenden Datenbank bereitgestellt).
Wie viele Orte haben Sie, wo Sie eine Liste von Entitäten desselben Typs erstellen und mehr als eine Entität mit der Standard-ID haben?
Würdest du dies als eine gute Alternative zur Erzeugung von Guid-Werten bei der Instanzerstellung betrachten?
Wenn Sie kein ORM verwenden, ist Ihre Vorgehensweise gut genug. Insbesondere, wenn eine Implementierung der Identitätskarte und Arbeitseinheit ist Ihre Verantwortlichkeit. Aber du hast nur Equals(object obj)
festgelegt. GetHashCode()
-Methode überprüft nicht, ob uniqueId.Equals(default(IdType))
.
Ich schlage vor, dass Sie in jede Open-Source- "Infrastructure Boilerplate" wie Sharp-Architecture schauen und sie überprüfen Implementierung der Basisklasse für alle Domain-Entitäten .
Ich bin es gewohnt, benutzerdefinierte Implementierungen von Equals()
für Domain-Entities zu schreiben, aber es kann überflüssig sein, wenn es um die Verwendung von ORM geht. Wenn Sie ein ORM verwenden, werden Implementierungen der Identitätskarte und Einheit der Arbeit Muster aus der Box und Sie können sich auf sie verlassen.
Sie können einen Sequenzgenerator verwenden, um eindeutige int
/ long
Identifikatoren zu generieren, wenn Sie ein Entitätsobjekt instanziieren.
Die Schnittstelle sieht wie folgt aus:
%Vor% Eine typische Implementierung eines Sequenzgenerators verwendet eine Sequenztabelle in der Datenbank. Die Sequenztabelle enthält zwei Spalten: sequenceName
und allocatedSequence
.
Wenn getNextSequence
zum ersten Mal aufgerufen wird, schreibt es einen großen Wert (zB 100
) in die Spalte allocatedSequence
und gibt 1
zurück. Der nächste Aufruf gibt 2
zurück, ohne auf die Datenbank zugreifen zu müssen. Wenn die 100
Sequenzen ablaufen, liest und inkrementiert es die allocatedSequence
by 100
erneut.
Sehen Sie sich den SequenceHiLoGenerator
im Hibernate-Quellcode an. Es macht grundsätzlich das, was ich oben beschrieben habe.
Ich glaube, die Lösung dafür ist eigentlich ziemlich einfach:
Wie Sie erwähnen, müssen Entitäten eine Identität haben,
Gemäß Ihren (vollkommen gültigen) Anforderungen wird die Identität Ihrer Entitäten zentral vom DBMS zugewiesen,
Daher ist jedes Objekt, dem noch keine Identität zugewiesen wurde, nicht eine Entität.
Was Sie hier behandeln, ist eine Art von Data Transfer Object-Typ, der keine Identität hat. Sie sollten darüber nachdenken, wie Sie Daten von jedem Eingabesystem, das Sie verwenden, über das Repository an das Domänenmodell übertragen (das Sie als Schnittstelle für die Identitätszuweisung benötigen). Ich schlage vor, dass Sie einen anderen Typ für diese Objekte (eines ohne Schlüssel) erstellen und an die Methode "Hinzufügen / Erstellen / Einfügen / Neu" Ihres Repositorys übergeben.
Wenn die Daten nicht viel Vorverarbeitung benötigen (d. h. nicht viel herumgereicht werden müssen), lassen manche Leute sogar DTOs weg und leiten die verschiedenen Daten über die Methodenargumente direkt weiter. So sollten Sie solche DTOs betrachten: als bequeme Argumentobjekte. Beachten Sie erneut das Fehlen eines "Schlüssel" - oder "ID" -Arguments.
Wenn Sie das Objekt als Entity bearbeiten müssen, bevor Sie es in die Datenbank einfügen, sind DBMS-Sequenzen die einzige Option. Beachten Sie, dass dies in der Regel relativ selten ist. Der einzige Grund, warum Sie dies tun müssen, ist, ob die Ergebnisse dieser Manipulationen den Objektstatus ändern, sodass Sie eine zweite Anforderung zum Aktualisieren in der Datenbank stellen müssen würde sicherlich lieber vermeiden.
Sehr oft sind "Erstellungs-" und "Modifikations" -Funktionalitäten in Anwendungen so eindeutig, dass Sie immer zuerst die Datensätze für die Entitäten in der Datenbank hinzufügen, bevor Sie sie später erneut abrufen, um sie zu ändern.
Sie werden sich zweifellos Sorgen über die Wiederverwendung von Code machen. Je nachdem, wie Sie Ihre Objekte konstruieren, möchten Sie wahrscheinlich eine Validierungslogik ausschließen, damit das Repository die Daten überprüfen kann, bevor Sie sie in die Datenbank einfügen. Beachten Sie, dass dies normalerweise unnötig ist, wenn Sie DBMS-Sequenzen verwenden, und dies könnte ein Grund dafür sein, dass manche Leute sie systematisch verwenden, selbst wenn sie sie nicht unbedingt benötigen. Abhängig von Ihren Leistungsanforderungen sollten Sie die obigen Kommentare berücksichtigen, da eine Sequenz einen zusätzlichen Rundlauf generiert, den Sie oft vermeiden können.
Disclaimer: Ich habe keine gründliche Kenntnis der kanonischen DDD, ich würde nicht wissen, ob das wirklich der empfohlene Ansatz ist, aber es macht Sinn für mich.
Ich füge auch hinzu, dass das Ändern des Verhaltens von Equals
(und anderer Methoden) auf der Grundlage dessen, ob das Objekt eine Entität oder ein einfaches Datenobjekt darstellt, meiner Meinung nach nicht ideal ist. Bei der von Ihnen verwendeten Technik müssen Sie außerdem sicherstellen, dass der Standardwert, den Sie für den Schlüssel verwenden, ordnungsgemäß aus der Wertdomäne in der gesamten Domänenlogik ausgeschlossen wird.
Wenn Sie immer noch diese Technik verwenden möchten, empfehle ich, einen dedizierten Typ für den Schlüssel zu verwenden. Bei diesem Typ wird der Schlüssel mit einem zusätzlichen Status versehen bzw. umschlossen, der angibt, ob der Schlüssel vorhanden ist oder nicht. Beachten Sie, dass diese Definition Nullable<T>
so sehr ähnelt, dass ich sie in Erwägung ziehen würde (Sie können die Syntax type?
in C # verwenden). Mit diesem Design ist es klarer, dass Sie dem Objekt erlauben, keine Identität zu haben (Null-Schlüssel). Es sollte auch offensichtlicher sein, warum das Design nicht ideal ist (wieder, meiner Meinung nach): Sie verwenden denselben Typ, um sowohl Entitäten als auch identitätslose Datenübertragungsobjekte darzustellen.
Ich kann an dieser Stelle nicht anfangen, Guid-Werte zu verwenden.
Ja, Sie können und das wäre eine Alternative. Guids wären nicht die primären Schlüssel Ihrer Datenbank, sondern würden auf der Ebene des Domänenmodells verwendet. Bei diesem Ansatz könnten Sie sogar zwei separate Modelle haben - ein Persistenzmodell mit Ints als Primärschlüssel und -linien als Attribute und ein anderes Modell, das Domänenmodell, in dem die Guids die Rolle von Identifikatoren spielen.
Auf diese Weise können Ihre Domänenobjekte ihre Identitäten erhalten, sobald sie erstellt wurden, und Persistenz ist nur eine der kleineren geschäftlichen Angelegenheiten.
Die andere mir bekannte Option ist die, die Sie beschrieben haben.
Ihre vorgeschlagene Lösung ist meiner Erfahrung nach vollkommen richtig. Ich habe diesen Ansatz ziemlich oft benutzt.
Wenn Sie etwas noch besser wollen, hier ist ein weiterer Vorschlag. Was wäre, wenn Sie einen Wert haben könnten, der ( für unsere Absichten und Zwecke ) so gut wie eine GUID ist, aber in eine long
passt? Was wäre, wenn es auch ohne ein Repository reaktiv wäre?
Mir ist klar, dass Ihre Tabelle momentan nur ein int
als PRIMARY KEY
enthält. Aber wenn Sie dies in long
oder für Ihre zukünftigen Tabellen ändern können, könnte mein Vorschlag für Sie von Interesse sein.
In Vorschlag: lokal eindeutige GUID-Alternative erkläre ich, wie man eine lokal einzigartige, neue, streng aufsteigende 64- Bit-Wert. Es ersetzt die Kombination aus Auto-Inkrement-ID und GUID.
Ich habe immer die Idee gemocht, sowohl eine numerische ID als auch eine GUID zu haben. Es ist wie zu sagen: "Dies ist die eindeutige Kennung der Entität. Und ... das ist ihre andere eindeutige Kennung." Sicher, Sie können einen Bereich außerhalb der Domain und der Sprache beibehalten, aber Sie haben damit das technische Problem, gleichzeitig und zu verwalten und die zusätzliche numerische ID zu verbergen. Wenn Sie eine einzelne ID haben möchten, die sowohl domänenfreundlich ist (mit einem Repository umbenannt werden kann, als auch eine benannte ID anstelle von GUID) und datenbankfreundlich (klein, schnell und aufsteigend), versuchen Sie meinen Vorschlag.
Ich warne Sie, dass die Implementierung schwierig sein kann, insbesondere in Bezug auf Kollisionen und Thread-Sicherheit. Ich habe noch keinen Code dafür geschrieben.
Tags und Links c# repository-pattern domain-driven-design aggregateroot domain-model