In fast allen Fällen gehören Primärschlüssel nicht zu Ihrer Geschäftsdomäne. Sicher, Sie haben möglicherweise einige wichtige benutzerbezogene Objekte mit eindeutigen Indizes ( UserName
für Benutzer oder OrderNumber
für Aufträge), aber in den meisten Fällen besteht keine geschäftliche Notwendigkeit , Domänenobjekte offen anhand eines einzelnen Wertes oder Wertesatzes zu identifizieren administrativer Benutzer. Selbst in diesen Ausnahmefällen, insbesondere wenn Sie GUIDs ( Global Unique Identifiers) verwenden , möchten Sie einen alternativen Schlüssel verwenden, anstatt den Primärschlüssel selbst bereitzustellen.
Wenn mein Verständnis von domänengetriebenem Design korrekt ist, müssen und sollten Primärschlüssel nicht offengelegt werden, und es sollte ein guter Befreiungsschlag erfolgen. Sie sind hässlich und verkrampfen meinen Stil. Wenn wir uns jedoch dafür entscheiden, keine Primärschlüssel in das Domänenmodell aufzunehmen, ergeben sich folgende Konsequenzen:
- Datenübertragungsobjekte ( Data Transfer Objects, DTO) , die ausschließlich aus Kombinationen von Domänenmodellen abgeleitet werden, verfügen nicht über Primärschlüssel
- Eingehende DTOs haben keinen Primärschlüssel
Ist es also sicher zu sagen, dass Sie, wenn Sie wirklich rein bleiben und Primärschlüssel in Ihrem Domain-Modell eliminieren möchten, bereit sein sollten, jede Anfrage in Bezug auf eindeutige Indizes für diesen Primärschlüssel zu bearbeiten?
Anders ausgedrückt: Welche der folgenden Lösungen ist der richtige Ansatz, um bestimmte Objekte nach dem Entfernen der PK in Domänenmodellen zu identifizieren?
- In der Lage zu sein, die Objekte, mit denen Sie sich befassen müssen, anhand anderer Attribute zu identifizieren
- Wiederherstellen des Primärschlüssels im DTO; dh die PK bei der Zuordnung von der Persistenz zur Domäne entfernen und dann die PK bei der Zuordnung von der Domäne zur DTO neu kombinieren?
EDIT: Lassen Sie uns dies konkretisieren.
Sagen Sie mein Domain - Modell ist , VoIPProvider
welches enthält Felder wie Name
, Description
, URL
, sowie Referenzen wie ProviderType
, PhysicalAddress
und Transactions
.
Angenommen, ich möchte einen Webdienst erstellen, mit dem privilegierte Benutzer VoIPProvider
s verwalten können .
Vielleicht ist eine benutzerfreundliche ID in diesem Fall nutzlos. VoIP-Anbieter sind schließlich Unternehmen, deren Namen aus geschäftlichen Gründen in der Regel im Computersinn und sogar im menschlichen Sinn eindeutig genug sind. Es mag also genügen zu sagen, dass ein Unikat VoIPProvider
vollständig von bestimmt wird (Name, URL)
. Angenommen, ich benötige eine Methode PUT api/providers/voip
, mit der privilegierte Benutzer VoIP
Anbieter aktualisieren können. Sie senden ein VoIPProviderDTO
, das viele, aber nicht alle Felder enthält VoIPProvider
, einschließlich einiger potenzieller Abflachungen. Ich kann ihre Gedanken jedoch nicht lesen und sie müssen mir immer noch mitteilen, über welchen Anbieter wir sprechen.
Anscheinend habe ich 2 (vielleicht 3) Optionen:
- Fügen Sie einen Primär- oder Alternativschlüssel in mein Domain-Modell ein und senden Sie ihn an das DTO und umgekehrt
- Identifizieren Sie den Anbieter, den wir interessieren, über den eindeutigen Index wie
(Name, Url)
- Führen Sie eine Art Zwischenobjekt ein, das immer auf eine Weise zwischen Persistenzschicht, Domäne und DTO abgebildet werden kann, die keine Implementierungsdetails über die Persistenzschicht preisgibt - beispielsweise durch Einfügen einer speicherinternen temporären ID, wenn von Domäne zu DTO und zurück gewechselt wird.
quelle
Antworten:
So lösen wir das (seit mehr als 15 Jahren, als nicht einmal der Begriff "domain driven design" erfunden wurde):
Wenn Sie also einen Primärschlüssel für technische Zwecke benötigen (z. B. zum Zuordnen von Beziehungen zu einer Datenbank), steht Ihnen dieser zur Verfügung. Wenn Sie ihn jedoch nicht "sehen" möchten, ändern Sie Ihre Abstraktionsebene in das Modell "Domänenexperten" ". Und Sie müssen nicht "zwei Modelle" pflegen (eines mit und eines ohne PKs). Pflegen Sie stattdessen nur ein Modell ohne PKs und erstellen Sie mithilfe eines Codegenerators die DDL für Ihre DB, die die PK automatisch gemäß den Zuordnungsregeln hinzufügt.
Beachten Sie, dass dies nicht verbietet, neben dem Ersatz "Geschäftsschlüssel" wie eine zusätzliche "Bestellnummer" hinzuzufügen
OrderID
. Technisch gesehen werden diese Geschäftsschlüssel bei der Zuordnung zu Ihrer Datenbank zu alternativen Schlüsseln. Vermeiden Sie es einfach, diese zum Erstellen von Verweisen auf andere Tabellen zu verwenden. Verwenden Sie nach Möglichkeit immer lieber die Ersatzschlüssel, da dies die Dinge erheblich vereinfacht.Zu Ihrem Kommentar: Die Verwendung eines Ersatzschlüssels zur Identifizierung von Datensätzen ist kein geschäftlicher Vorgang, sondern ein rein technischer Vorgang. Um dies zu verdeutlichen, sehen Sie sich Ihr Beispiel an: Solange Sie keine zusätzlichen Unique-Contraints definieren, können zwei VoIPProvider-Objekte mit derselben Kombination aus (Name, URL), aber unterschiedlichen VoIPProviderIDs vorhanden sein.
quelle
Sie müssen in der Lage sein, viele Objekte anhand eines eindeutigen Index zu identifizieren, und es ist nicht so, dass ein Primärschlüssel vorhanden ist (oder zumindest impliziert, dass einer vorhanden ist).
Es stehen eindeutige Indizes zur Verfügung, mit denen Sie Ihr DB-Schema weiter einschränken können, nicht als Wholesale-Ersatz für PKs. Wenn Sie keine PKs bereitstellen, weil sie hässlich sind, sondern stattdessen einen eindeutigen Schlüssel bereitstellen ... tun Sie eigentlich nichts anderes. (Ich nehme an, dass Sie hier keine PK- und Identitätsspalte verwechseln?)
quelle
Ohne Primärschlüssel im Frontend gibt es keine einfache Möglichkeit für das Backend zu wissen, was Sie senden. Um dies zu beheben, müssten Sie eine Menge zusätzlicher Arbeit aufwenden, um die Daten zu analysieren. Dies würde die Leistung beeinträchtigen und wahrscheinlich mehr Zeit in Anspruch nehmen und hässlicher sein als das Anhängen eines Schlüssels an jedes Element.
Nehmen wir als Beispiel an, ich möchte eine Nachricht in einer App bearbeiten. Woher weiß die App, welche Nachricht ich ohne Primärschlüssel bearbeiten möchte? Das Bearbeiten von Objekten geschieht ständig und ohne Schlüssel ist dies nahezu unmöglich. Wenn Sie jedoch Objekte haben, die nicht bearbeitet werden sollen, überspringen Sie den Schlüssel, wenn Sie der Meinung sind, dass er ablenkt. Wenn Sie jedoch Primärschlüssel haben, kann dies die Leistung verbessern.
quelle
MessageSender
,MessageRecipient
,TimeSent
- das sollte eindeutig sein.Der Grund, warum wir eine nicht geschäftsbezogene PK verwenden, besteht darin, sicherzustellen, dass unser System über eine einfache und konsistente Methode verfügt, mit der ermittelt werden kann, was der Benutzer möchte.
Ich sehe, dass Sie mit einem Kommentar geantwortet haben: MessageSender, MessageRecipient, TimeSent (für eine Nachricht). Sie können auf diese Weise IMMER NOCH Mehrdeutigkeiten aufweisen (z. B. wenn vom System generierte Nachrichten aufgrund von Ereignissen ausgelöst werden, die häufig auftreten). Und wie werden Sie MessageSender und MessageRecipient hier validieren? Angenommen, Sie validieren sie mit FirstName, Lastname, DateOfBirth, und Sie werden irgendwann auf eine Situation stoßen, in der Sie zwei Personen haben, die am selben Tag mit genau demselben Namen geboren wurden. Ganz zu schweigen davon, dass Sie in eine Situation geraten, in der Sie eine Nachricht mit dem Namen haben
tacostacostacos-America-1980-Doc Brown-France-1965-23/5/2014-11:43:54.003UTC+200
. Das ist ein Monster mit einem Namen, und Sie können immer noch nicht garantieren, dass Sie nur einen davon haben.Der Grund, warum wir einen Primärschlüssel verwenden, ist, dass wir wissen, dass er für die gesamte Lebensdauer der Software eindeutig ist, unabhängig davon, welche Daten eingegeben werden. Wir wissen, dass er ein vorhersehbares Format hat (was passiert, wenn Ihr oben genannter Schlüssel einen Bindestrich hat) In einem Benutzernamen geht dein ganzes System in die Scheiße.
Sie müssen Ihrem Benutzer Ihre ID nicht zeigen. Sie können dies verbergen (bei Bedarf über die URL im Klartext).
Ein weiterer Grund, warum ein PK so nützlich ist, lässt sich aus den obigen Ausführungen ableiten: Ein PK macht es so, dass Sie den Computer nicht zwingen müssen, vom Benutzer generierten Code zu interpretieren. Wenn ein chinesischer Benutzer Ihren Code verwendet und eine Reihe chinesischer Zeichen eingibt, muss Ihr Code plötzlich nicht mehr intern mit diesen arbeiten können, sondern kann einfach die vom System generierte Guid verwenden. Wenn Sie einen arabischen Benutzer haben, der arabische Schriften eingibt, muss Ihr System das nicht intern bewältigen, sondern kann im Grunde ignorieren, dass er dort ist.
Wie bereits erwähnt, kann eine Guid intern in einer festen Größe gespeichert werden. Sie wissen, womit Sie arbeiten, und es ist etwas, das universell einsetzbar ist. Sie müssen keine Entwurfsregeln dafür erstellen, wie Sie einen bestimmten Bezeichner erstellen und speichern. Wenn Ihr System nur die ersten 10 Buchstaben eines Namens akzeptiert, erkennt es keinen Unterschied zwischen Michael Guggenheimer und Michael Gugstein und verwirrt diese 2. Wenn Sie es in einer beliebigen Länge abschneiden, können Sie in Verwirrung geraten. Wenn Sie die Benutzereingabe begrenzen, können Probleme mit Benutzerbeschränkungen auftreten.
Wenn ich vorhandene Systeme wie Dynamics CRM betrachte, verwenden sie auch den internen Schlüssel (PK), mit dem der Benutzer einen einzelnen Datensatz abrufen kann. Wenn ein Benutzer eine Abfrage hat, zu der die ID nicht gehört, gibt er eine Reihe möglicher Antworten zurück und lässt den Benutzer eine Auswahl treffen. Wenn es eine Möglichkeit zur Mehrdeutigkeit gibt, geben sie dem Benutzer die Wahl.
Schließlich ist es auch ein gewisser Sicherheitsfaktor durch Unbekanntheit. Wenn Sie die Datensatz-ID nicht kennen, können Sie sie nur erraten. Wenn die ID leicht zu erraten ist (weil die Informationen, aus denen sie bestehen, öffentlich verfügbar sind), kann sie jeder ändern. Sie können den Benutzer sogar dazu anregen, ihn mithilfe klassischer CSRF- oder XSS-Methoden zu ändern. Natürlich sollte Ihre Sicherheit diese bereits berücksichtigt und gemindert haben, bevor Sie die Live-Version veröffentlichen. Sie sollten es jedoch immer noch schwieriger machen, potenziellen Missbrauch zu erleiden.
quelle
Wenn Sie einen Bezeichner für ein externes System ausgeben, sollten Sie nur URIs oder alternativ einen Schlüssel oder eine Reihe von Schlüsseln angeben, die dieselben Eigenschaften wie ein URI haben, anstatt den Datenbankprimärschlüssel direkt bereitzustellen (von hier an verweise ich darauf beide URIs oder ein Schlüssel oder eine Reihe von Schlüsseln, die die gleichen Eigenschaften wie URIs wie nur URIs haben, mit anderen Worten, ein URI darunter bedeutet nicht notwendigerweise RFC 3986 URI).
Ein URI kann den Primärschlüssel des Objekts enthalten oder nicht, und er kann tatsächlich aus alternativen Schlüsseln bestehen oder nicht. Es ist nicht wirklich wichtig. Was zählt, ist, dass nur das System, das den URI generiert, den URI aufteilen oder kombinieren darf, um ein Verständnis dafür zu erhalten, was das referenzierte Objekt ist. Externe Systeme sollten den URI immer als undurchsichtige Kennung verwenden. Es spielt keine Rolle, ob ein menschlicher Benutzer erkennen kann, dass ein Teil des URI tatsächlich ein Datenbank-Ersatzschlüssel ist oder aus mehreren zusammengebündelten Geschäftsschlüsseln besteht oder dass es sich tatsächlich um eine Base64 dieser Werte handelt. Diese sind irrelevant. Was zählt, ist, dass das externe System nicht erforderlich sein sollte, um zu verstehen, was der Bezeichner bedeutet, um den Bezeichner zu verwenden. Externe Systeme sollten niemals aufgefordert werden, die Komponenten innerhalb des Bezeichners zu analysieren oder einen Bezeichner mit anderen Bezeichnern zu kombinieren, um auf etwas in Ihrem System zu verweisen.
Die Verwendung von GUID erfüllt einige dieser Kriterien. Allerdings kann es schwierig sein, Identifikatoren wie GUID in das Objekt zurückzuverweisen, auch wenn sie sich in Ihrem System befinden. Daher sollten Schlüssel, die für Ihr System undurchsichtig sind, wie GUIDs, nur verwendet werden, wenn der Client den URI / Identifikator tatsächlich analysiert ein Sicherheitsrisiko darstellt.
Gehen Sie zu Ihrem VoIP-Beispiel zurück und sagen Sie, dass ein VoIP-Anbieter entweder durch (VoIPProviderID) oder durch (Name, URL) oder durch (GUID) eindeutig bestimmt werden kann. Wenn ein externes System den VoIP-Provider aktualisieren muss, kann es einfach einen PUT / provider / by-id / 1234 übergeben oder
PUT /provider/foo-voip/bar-domain.com
oderPUT /3F2504E0-4F89-41D3-9A0C-0305E82C3301
und Ihr System würde verstehen, dass das externe System den VoIPProvider aktualisieren möchte. Diese URIs werden von Ihrem System generiert und nur Ihr System muss verstehen, dass sie alle dasselbe bedeuten. Das externe System sollte einfach alles, was in der URI enthalten ist, als grundlegend behandelnPUT <whatever>
.Angenommen, Sie haben die Daten für verschiedene VoIP-Anbieter in verschiedenen Tabellen mit unterschiedlichen Schemata gespeichert (daher identifiziert ein völlig unterschiedlicher Schlüsselsatz jeden VoIP-Anbieter anhand der Tabelle, in der sie gespeichert sind). Wenn Sie eine URI haben, kann das externe System einheitlich auf sie zugreifen, ohne darauf zu achten, wie Ihr System den jeweiligen VoIP-Anbieter identifiziert. Zum externen System ist alles nur ein undurchsichtiger Zeiger.
Wenn Ihr System einen URI verwendet, um auf Objekte so zu verweisen, verlieren Sie nichts darüber, wie Sie Ihr System implementieren. Sie generieren den URI und der Client gibt ihn einfach an Sie zurück.
quelle
Ich werde diese wild ungenaue und naive Aussage ins Visier nehmen müssen:
Namen sind schrecklich wie Schlüssel, da sie sich häufig ändern. Eine Firma kann zu Lebzeiten viele Namen haben, die Firma kann fusionieren, splitten, wieder fusionieren, eine eigene Tochtergesellschaft für bestimmte Steuerzwecke gründen, die 0 Angestellte hat, aber alle Kunden, die dann Angestellte einer völlig anderen Tochtergesellschaft einstellen.
Dann stellen wir fest, dass Firmennamen nicht einmal im Entferntesten eindeutig sind, wie der Meilenstein Apple vs. Apple zeigt .
Ein guter relationaler Mapper oder Framework für Objekte sollte die Primärschlüssel abstrahieren und diese unsichtbar machen, aber sie sind vorhanden, und sie sind normalerweise die einzige Möglichkeit, ein Objekt in Ihrer Datenbank eindeutig zu identifizieren.
Als Referenz bevorzuge ich, wie Django damit umgeht :
Auf diese Weise können die Details eines Anbieters eines Kunden mit folgendem Code abgerufen werden:
Die Primär- / Fremdschlüssel sind zwar nicht sichtbar, befinden sich jedoch dort und können für den Zugriff auf Elemente verwendet werden, werden jedoch abstrahiert.
quelle
Ich denke, wir betrachten dieses Problem aus Sicht der DB oft noch immer falsch: Oh, es gibt keinen natürlichen Schlüssel, daher müssen wir einen Ersatzschlüssel erstellen. Oh nein, wir können den Ersatzschlüssel nicht wieder in den Domänenobjekten offen legen, das ist undicht usw.
Manchmal ist dies jedoch eine bessere Einstellung: Wenn ein Geschäftsobjekt (Domain) keinen natürlichen Schlüssel hat, sollte ihm möglicherweise einer gegeben werden. Hierbei handelt es sich um ein zweifaches Business-Domain-Problem: Erstens benötigen die Dinge eine Identität, auch wenn keine Datenbanken vorhanden sind. Zweitens, obwohl wir versuchen, so zu tun, als sei Persistenz eine abstrakte Idee, die für die Domäne unsichtbar ist, ist die Realität, dass Persistenz immer noch ein Geschäftskonzept ist. Offensichtlich gibt es Probleme, bei denen der ausgewählte natürliche Schlüssel von der Datenbank nicht als Primärschlüssel unterstützt wird (z. B. GUIDs auf einigen Systemen). In diesem Fall müssen Sie einen Ersatzschlüssel hinzufügen.
So landen Sie an einem sehr ähnlichen Ort, z. B. wenn Ihr Kunde eine ganzzahlige ID hat. Anstatt sich jedoch krank zu fühlen, weil diese von der DB an die Domain gelangt ist, sind Sie glücklich, weil das Unternehmen vereinbart hat, dass allen Kunden eine ID zugewiesen wird ID, und Sie bestehen darauf, dass die DB, wie Sie müssen. Es steht Ihnen weiterhin frei, einen Ersatz einzuführen, z. B. um die Umbenennung der Kunden-ID zu unterstützen.
Dieser Ansatz bedeutet auch, dass ein Domänenobjekt, das auf die Persistenzschicht trifft und keine ID hat, wahrscheinlich ein Wertobjekt ist und daher keine ID benötigt.
quelle