Entitäten und deren Identifier

Sep 9, 2023

development

Da ich letztes Mal über Entitäten philosophiert habe, möchte ich diesmal über Identifiers philosophieren.

Gedanken

Identifier helfen dabei, manche Entitäten – eindeutig – zu benennen. Diesmal starte ich unten im Code-Model und mit einem technischen Detail: Wie gestalte ich die Tabellen am besten?

CREATE TABLE schema.table
(
    id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY

meistens findet man sowas vor. Die erste Frage, welche sich stellt: Sollte man diese id nach außen hin zur Verfügung stellen? E.g. in einer API wie /persons/{id}? Meine Meinung dazu: nein. Einige Gründe:

  • Refactoring von einer Tabelle zu Table inheritance: Hier haben die Tabellen eigene Id Spalten.
  • Wechseln von einem einfachen Primary-Key zu einem Composite-Primary-Key (PK über mehrere Spalten) - braucht man oft bei Constraints
  • Normalization -/Denormalization (z.B. Order und OrderItem Table wird zu CustomerOrders - man müsste hier den PK der Order als Feld von CustomerOrders machen, welches aber alleine inkrementiert)
  • Importieren von Daten aus u.a. mehreren Quellen, die bereits einen Identifier haben oder Database-Merge

Aber es gibt auch technische Gründe, wie folgende Artikel zeigen:

Daher die Empfehlung:

CREATE TABLE schema.table
(
    id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    external_id uuid NOT NULL UNIQUE

UUID – aber welche?

Einer der kompliziertesten OpenAPI Specs die ich je gesehen haben, sind jene aus dem TM-Forum. Arbeitet man mit diesen, kommt man oft in folgende Situation:

Entwickler A: Kannst du dir bitte mal Id e3c5c1df-f871-4c24-b7f0-6f882f5f0a01 anschauen?

Entwickler B: Was ist das für eine Id?

Entwickler A: Ich glaube es ist irgendein Preis – aber genau weiß ich es auch nicht

Wie könnte man hier entgegenwirken? Eine Möglichkeit ist natürlich Uniform Resource Name (URN).

urn:ISBN:3-8273-7019-1 lässt niemanden mehr rätseln, was 3-8273-7019-1 ist.

Andere Beispiele sind:

urn:warehouse:products:9076aa3a-348a-40b8-bf82-bcaad86ea21a

Identifier korrekt im Code

Auch im Code gibt es oft die Aufgabe

Entwickler A: Such mir bitte alle Stellen raus, wo wir mit der Product Id hantieren – wir müssen den Typ ändern und einige Well-Known-Ids unterbinden

Entwickler B: Das kann dauern … die Variable “productId” ist oft nicht immer das Gleiche … Manchmal heißt es auch nur Id und manchmal wurde die UUID zu einem String gemapped …

Auf den Variablen Namen “productId” kann man sich kaum verlassen. Daher verwende ich immer gerne ValueObjects. Gott sei Dank kennt C# inzwischen Records, da diese automatisch immutable sind.

ProductId mit dem passenden Namespace findet man daher sehr leicht.

URN in der Datenbank

Auch in der Datenbank kann man mit URN hantieren. Immer im Hinterkopf muss man natürlich Indezeses behalten. Dies können natürlich computed sein - wie folgendes Beispiel zeigt:

CREATE INDEX test1_lower_col1_idx ON test1 (lower(col1));

Aber das mit und ohne URN Supported geht, funktioniert nur mit Operatoren. Und selbst da muss man aufpassen, dass er den Operator nimmt - sonst kommt es zu einem Textvergleich und urn:ISBN:3-8273-7019-1 ist natürlich nicht 3-8273-7019-1.

Domain-Driven-Design und Identifier

Auch in dem DDD wird dem Identifier gorße Bedeutung zugeschrieben. Folgende Seiten zeigen interessante Ideen:

  • https://ddd-practitioners.com/home/glossary/entity-identity/
    • Hier wird nochmal mit Nachdruck drauf hingewiesen, dass der Identifier eindeutig und stabil sehen sollte. Die menschliche Lesbarkeit kann man mit URN nochmal erhöhen und trptzdem die Vorteile einer UUID nutzen.
  • In DDD ist der Identifier auch ausschlaggebend für ein Entity - im Kontrast zu einem ValueObject
  • DDD empfiehlt auch “natural identifiers”. Beispiel ist für mich WhatsApp mit einer Telefonnummer oder Microsoft Online Dienste mit einer Email:

In some cases, the uniqueness of the ID must apply beyond the computer system’s boundaries. For example, if medical records are being exchanged between two hospitals that have separate computer systems, ideally each system will use the same patient ID, but this is difficult if they generate their own symbol. Such systems often use an identifier issued by some other institution, typically a government agency. In the United States, the Social Security number is often used by hospitals as an identifier for a person. Such methods are not foolproof. Not everyone has a Social Security number (children and nonresidents of the United States, especially), and many people object to its use, for privacy reasons.

In less formal situations (say, video rental), telephone numbers are used as identifiers. But a telephone can be shared. The number can change. An old number can even be reassigned to a different person.

For these reasons, specially assigned identifiers are often used (such as frequent flier numbers), and other attributes, such as phone numbers and Social Security numbers, are used to match and verify. In any case, when the application requires an external ID, the users of the system become responsible for supplying IDs that are unique, and the system must give them adequate tools to handle exceptions that arise.

Quelle: Evans, E. (2004). Domain-driven design: tackling complexity in the heart of software. Addison-Wesley Professional.

Fazit

Identifier sind extrem wichtig und sollten einen festen Bestandteil im Design haben.