DDD ID Generierung

Die Identität einer Entity hängt bei Domänengetriebener Entwicklung einzig und allein von der … Identität der Entity ab. Das bedeutet logischerweise, dass in einer equals-Methode einzig und allein diese ID als Vergleichswert zulässig ist. Das wiederum hat zur Folge, dass die ID bereits vor der Persistierung bekannt sein muss.
Wie generiert man am geschicktesten eine solche ID?

*** Edit ***

Ich möchte @maki einmal zitieren und diese Aussage hier zur Diskussion stellen.

[QUOTE=maki]Einen “IdGenerator” zu schreiben ist sehr simpel, AtomicInteger/AtomicLong welcher mit dem hoechsten ID Wert aus der DB + 1 beim starten der Anwendung initialisiert wird, fertig, machen ORMs nicht anders.
UUID gehen auch, haben aber auswirkungen auf RDBMS.

Dieser IdGenerator kann dann in die Factory injiziert werden, einen pro Entity, oder einen fuer alle Entities.

Ist wirklich nicht schwer wenn man sich mental vom Konzept loest dass die Persistenz das machen sollte…[/QUOTE]

Das hört sich für mich schonmal ziemlich plausibel an. Problematisch wird das dann wahrscheinlich, wenn die Anwendung auf mehreren Knoten läuft.
Wäre dann eine sinnvolle Vorgehensweise, bei n Knoten nur jede n-te ID zu vergeben?

ich vermute Du wolltest das gleiche wie ich - nur falsch ausgedrückt

wenn Du jeden n-te ID nimmst für den n-ten Knoten
[ul]
[li]Konten 1 → 1/2/3/4/…[/li][li]Knoten 2 → 2/4/6/8/…[/li][li]…[/li][/ul]

Du brauchst eher etwas wie ID + Knotenanzahl (bei 3 Knoten)

[ul]
[li]Knoten 1 → 1/4/7/…[/li][li]Knoten 2 → 2/5/8/…[/li][li]Knoten 3 → 3/6/9/…[/li][/ul]

Hat aber den Nachteil das Du leere Stellen bekommst, wenn ein Knoten mehr/weniger ID erzeugt als der Rest. Ist die gleichmäßig ist das Problem vernachlässigbar.

Alternativ kümmert sich ein Knoten um das erzeugen der ID, die anderen Knoten fragen dort nach. Hat den Vorteil das Du schön Synchronisieren kannst und keine leeren Stellen bekommst.

Ja, wir haben das gleiche gemeint. Mathematisch gesehen habe ich mich aber korrekt ausgedrückt :wink: n ist die Gesamtzahl der Knoten, unabhängig davon, welchen Knoten man betrachtet.

Das ist aber eher Kosmetik für den Menschen als ein echter Nachteil. Dem DBMS ist das egal.
Der einzige Nachteil, der mir einfällt wäre, dass die IDs schneller aufgebraucht werden. Das ist bei passender Wahl des ID-Typs aber wohl auch eher ein theoretisches Problem. Und wenn man per-Class-IDs verwendet, reichen selbst die IDs, die man in einem int speichern kann, ziemlich lange.

Das hat aber einen Single Point of Failure zur Folge. Außerdem könnte man dann auch gleich das DBMS die Generierung der IDs übernehmen lassen, z. B. indem man eine Hilfstabelle nimmt, mit der die IDs generiert werden.
Dabei werden dann auch Netzwerkzugriffe notwendig und man bekommt für jede zu generierende ID einen Netzwerkzugriff. Batch-Inserts werden dadurch zur Farce.

genau n zu vergeben läßt keine Möglichkeit auf Erweiterung der Knoten zu, kann ok sein, vielleicht auch ungünstig,

lose eingeworfen ohne Praxisbewährung könnte man zumindest die Anzahl möglicher Knoten massiv erweitern, z.B. 1000 freihalten,
ein Knoten bekommt 001 oder 002 usw.,
entweder als Endziffern, für 001 dann mögliche Ids 1001, 2001, 3001,

oder vorneweg und dahinter z.B. 9 Stellen (00)1.000.000.001, 1.000.000.002, 1.000.000.003…

letzeres limitiert die Ids, beides treibt die Wertebereiche gewiss hoch


das müsste ja nicht einzeln passieren, man kann doch bestimmt auch ganze Blöcke reservieren, oder wieder selber dazu bauen:
Wert 17 aus der DB bekommen bedeutet Erlaubnis, alle Ids von 17.000 bis 17.999 zu verwenden

Da hast du auf jeden Fall Recht.

Die Idee, ID-Blöcke zu vergeben, wäre sicherlich ein guter Kompromiss aus Anzahl Requests und ID-Abdeckung.

Allerdings geht es mir vorerst weniger um die Generierung von IDs auf mehreren Knoten, als um die Generierung der IDs vor der Persistierung. Multiknoten-Anwendungen verkomplizieren die Problematik natürlich, wie im Ausgangsposting ja schon angedeutet.

Die im Ausgangsposting zitierte Vorgehensweise finde ich ehrlich gesagt schon ziemlich attraktiv, aber das wird sicherlich nicht die einzige Möglichkeit sein, um die IDs zu generieren.
Sobald wir hier brauchbare Lösungen gefunden haben, um IDs zu vergeben, möchte ich gerne noch einmal das Multiknoten-Problem diskutieren.

*** Edit ***

Eine Möglichkeit IDs zu generieren ist natürlich, das DBMS zu involvieren. Ob mit Blockzuweisungen oder mit Einzel-ID-Generierung. Dieser Weg besitzt aber nicht die Eleganz eines einfachen AtomicInteger, bei dem das alles Clientseitig läuft.

Ganz unabhängig von der Methodik wäre solch ein Interface geeignet, um einen ID-Generator abzubilden:

public interface IdGenerator {
    int nextId();
}```

Ein auf einem AtomicInteger beruhender IdGenerator sähe dann so aus:
```public class LocalIdGenerator implements IdGenerator {
    private final AtomicInteger currentId;

    public LocalIdGenerator(int initialId) {
        currentId = new AtomicInteger(initialId);
    }

    @Override
    public int nextId() {
        return currentId.incrementAndGet();
    }
}```

einer, der die "jeder n-te Knoten" Strategie implementiert so:
```public class LocalMultinodeIdGenerator implements IdGenerator {
    private final AtomicInteger currentId;
    private final int numberOfNodes;

    public LocalMultinodeIdGenerator(int initialId, int numberOfNodes) {
        currentId = new AtomicInteger(initialId);
        this.numberOfNodes = numberOfNodes;
    }

    @Override
    public int nextId() {
        return currentId.addAndGet(numberOfNodes);
    }
}```

*** Edit ***

Ein "DbmsIdGenerator" sähe ungleich komplexer aus und lässt sich nicht mal eben so herunterimplementieren.
Die Schwierigkeit liegt beim LocalIdGenerator allein in der Initialisierung.

Du könntest deine Id auch ein 2 Teile aufspalten, eine Knoten-Id und eine Id des Objekts, aber das wäre ja auch nur eine Form von Block-Id. Die Id des Knoten könnte dieser dann einmal abfragen und dann immer verwenden.
Die Id eines Objekts besteht dann natürlich aus beiden Teilen. Hat nachher vielleicht auch noch den Vorteil, das man bestimmte Objekte einem bestimmten Knoten zuordnen kann.

Ich hab dir mal was gegooglet: http://de.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems

Composite Key… Ganz schlechte Praxis. Und ein technisches Problem landet im Domänenmodell, was zu vermeiden ist.
Wenn man stringent nur jede n-te Id vergibt und die Knoten immer im gleichen Modul (nicht im Sinne von Klassenverteilung, sondern im mathematischen Sinn) arbeiten, dann kann man den Knoten anhand id % n berechnen. Abgesehen davon sollten Knoten für die Anwendung möglichst unsichtbar sein, was den Nutzen zudem verringert.

Das zielt wenn ich den Inhalt richtig verstanden habe, auf UUIDs ab (der „jeder n-te Knoten-Ansatz“ wurde anfangs ja auch erwähnt). Die Pros- und Contras findet man im Netz zuhauf - mMn wiegen die Nachteile deutlich schwerer.

Nein, da steht z.B mit UUID kannst du de MAC-Adresse des ID-generierenden Servers + der Erstellungszeit (in Nanosekunden) als ID verwenden. Es gibt aber auch Alternativen wie Twitter Snowflake und Boundary Flake, die Links sind am Schluss der Präsentation.

Das sind aber alles Formen einer weltweit eindeutigen ID, weshalb sie parallel generiert werden können. Auf die MAC-Adresse würde ich mich aber wahrscheinlich nicht verlassen, wenn die Knoten in einer virtualisierten Umgebung laufen.

Mal angenommen, es sei das oben genannte Interface gegeben:

public interface IdGenerator<T extends Serializable> {
    T nextId();
}

Dessen Implementierungen interessieren mich erst einmal gar nicht. Wie bekommt jetzt jede Entity am geschicktesten vor der Persistierung eine ID?

Ich habe auf dem Problem mittlerweile schon eine Zeit lang herumgedacht und eigentlich nur folgende Lösung gefunden:
[ol][li]Entities werden immer über Factories erzeugt[/li][li]in die Factories werden IdGeneratoren injiziert[/li][li]für jede Entity gibt es einen IdGenerator, der mit der [japi]Named[/japi]-Annotation benannt wird[/li][li]in die IdGeneratoren wird jeweils ein entityspezifisches Repository injiziert, welches eine Methode findLastId hat, damit kann der Generator initialisiert werden[/ol][/li]
Bei datenbankweit eindeutigen IDs (zu denen UUIDs (inkl. aller Geschmacksrichtungen wie den *flakes), wie auch datenbankweite sequenzen zählen) benötigt man nur die Schritte 1 und 2, weil es nur genau einen ID-Generator gibt.
3 und 4 werden dementsprechend nur bei entityspezifischen IDs benötigt.

Gibt es für entityspezifische IDs vielleicht einen eleganteren Weg, um alles zu verdrahten? Ist das Vorgehen an sich vielleicht schon verkompliziert?

*** Edit ***

Was mich dabei stört ist, dass eigentlich nur die Initialisierung der IdGeneratoren unterschiedlich ist. Wenn die Generatoren sich extern initialisieren ließen, indem ein Konstruktorargument den Initialisierungswert aufnimmt, bräuchte man nur angepasste IdGeneratoren in die EntityFactories zu injizieren.
Für dieses Problem fehlt mir eine (technische) Lösung.

Auf die MAC-Adresse würde ich mich aber wahrscheinlich nicht verlassen, wenn die Knoten in einer virtualisierten Umgebung laufen.

Warum nicht? Falls die Mac dich so sehr stört kannst du immer noch jedem Knoten per hand eine eigene ID-geben, die du der Entity ID voranstellst. Der Punkt ist, dass du mit der möglichen Länge einer UUID so viel Adress-Spielraum bei der Benennung deiner Entities hast, dass irgendwelche n-Inkremente komplett überflüssig sind. Du kannst deinen ID-Generatoren beispielsweise Namen geben, wodurch einer eine UUID: Heinz-328032582345i6u34869346803486340683 und ein andere eine UUID: Fritz-3428083240235093285092u3t09823509235 erstellt. Dann übergibst du per Konstruktor deinem ID-Generator eben Heinz oder Fritz.

Weil ich nicht weiß, wie die Virtualisierungslösungen die MAC-Adressen generieren. Auf einem einzelnen Virtualisierungsserver werden die Adressen sicherlich nicht doppelt vergeben, aber bei zwei unabhängig laufenden Instanzen kommt es eben auf den Generierungsalgorithmus an.

Die Umsetzung eines ID-Generators ist hierbei eigentlich das kleinste Problem. Da gilt es die Punkte
[ul][li]Vergabestrategie
[/li][li]Größe der ID
[/li][li]Performance im DBMS[/ul]
[/li]abzuwägen.

Ich habe derzeit aber folgende Schwierigkeiten, die ich hier gern diskutieren möchte:
[ul][li]Initialisierung und Injizierung der IdGeneratoren (nur bei per-Entity-Ids)
[/li][li]Prüfung, ob eine Entity bereits persistiert wurde oder nicht (geht nicht mehr via id != null - wie ginge das möglichst noninvasiv?)
[/li][*]Übergabe von neuen Entities über eine Webschnittstelle (DTO?)[/ul]

Wie wäre es mit einer UID wie z.b. die ObjectId von MongoDB?

ObjectId is a 12-byte type, constructed using:

[ul]
[li]a 4-byte value representing the seconds since the Unix epoch,[/li]> [li]a 3-byte machine identifier,[/li]> [li]a 2-byte process id, and[/li]> [li]a 3-byte counter, starting with a random value.[/li]> [/ul]

Wäre dann sogar Multiknoten sicher ^^

Wie gesagt, die Gestaltung der ID an sich halte ich mittlerweile für nicht mehr problematisch. Mir geht es vorerst nur noch um

[QUOTE=cmrudolph][…] folgende Schwierigkeiten, die ich hier gern diskutieren möchte:
[ul][li]Initialisierung und Injizierung der IdGeneratoren (nur bei per-Entity-Ids)
[/li][li]Prüfung, ob eine Entity bereits persistiert wurde oder nicht (geht nicht mehr via id != null - wie ginge das möglichst noninvasiv?)
[/li][li]Übergabe von neuen Entities über eine Webschnittstelle (DTO?)[/ul][/QUOTE]
[/li]
Mir ist klar, dass das technische Probleme sind. Daher vielleicht etwas zum verwendeten Stack. Ich nutze Java 8, Spring 4 und Spring Data JPA 1.6 mit Hibernate 4 (Datenbank ist derzeit eine MySQL 5.6).

Am besten wären natürlich Lösungen, die unabhängig von den verwendeten Technologien funktionieren (DI und JPA funktionieren ja weitestgehend unabhängig von der Implementierung).
Allerdings kann man wahrscheinlich gerade bei der Initialisierung der IdGeneratoren einige Spring-Features nutzen, die die Umsetzung erleichtern.
Die Prüfung, ob eine Entity bereits persistiert wurde, ist fast ausschließlich auf Spring Data bezogen, das Konzept dahinter allerdings nicht (z. B. ein Statusflag innerhalb der Entity).

*** Edit ***

Dafür habe ich jetzt eine Lösung mit einem Statusflag und JPA lifecycle events:

import javax.annotation.Nonnull;
import javax.persistence.*;
import java.io.Serializable;

import static com.google.common.base.Preconditions.checkNotNull;

@MappedSuperclass
public class AbstractEarlyIdPersistable<ID extends Serializable> implements Persistable<ID> {
    private static final long serialVersionUID = -5020502021329768695L;
    @Id
    private ID id;
    @Transient
    private boolean isNew = true;

    protected AbstractEarlyIdPersistable() {
    }

    public AbstractEarlyIdPersistable(@Nonnull ID id) {
        this.id = checkNotNull(id);
    }

    @Override
    public ID getId() {
        return id;
    }

    @Override
    public boolean isNew() {
        return isNew;
    }

    @PrePersist
    private void checkId() {
        if (id == null)
            throw new IllegalStateException(String.format("Entity of class %s should get persistet but has no id set.",
                    this.getClass().getCanonicalName()));
    }

    @PostPersist
    @PostLoad
    private void markPersisted() {
        isNew = false;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof AbstractEarlyIdPersistable)) return false;

        AbstractEarlyIdPersistable that = (AbstractEarlyIdPersistable) o;

        return id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }
}

Noninvasiv ist dabei relativ zu sehen, weil alle Entities von der Klasse erben müssen.

Hallo cmdrudolph,
ein paar Denkansöße zum persisted flag.

  • Das Feld genauso zu nennen, wie den accessor (isNew) finde ich unschön (letztlich aber nur Geschmacksfrage).
  • Denke darüber nach, die Semantik des flags umzudrehen. Anstatt isNew also ein isPersisted. Das hat zwei Vorteile. Erstens wird die Bedeutung des Flags klarer. Ohne das Hintergrundwissen, dass es hier um Persistierung geht, könnte man sich fragen, wann eine Entity denn “neu” ist. Zweitens sparst du dir die explizite Zuweisung von “true” bei der Initialisierung.
  • Der Nutzen des boolean-Flags ist ziemlich eingeschränkt. Denke darüber nach, hier ein Long/Integer zu verwenden. Ist dieser null, ist die Entity noch nicht persistiert. Ist dieser non-Null ist sie persistiert. Später könnte man mit einem solchen Feld (welches treffenderweise “version” zu nennen wäre) optimistic locking umsetzen, wenn man es denn braucht. Wenn es ein Version-Feld gibt, wird dessen Null/Not-Null-Zustand übrigens (zumindest von Hibernate) zur Erkennung genutzt, ob die Entity bereits persisitert ist. Du bist damit also out of the box näher an ORM-Frameworks dran als mit Deinen eigenimplementierten Hooks.

Das kam mir auch einen ganz kurzen Moment in den Sinn. Der Accessorname kommt allerdings von Spring Data (im Interface Persistable definiert). Dort wird diese Methode verwendet, um zu prüfen, ob em.merge oder em.persist aufgerufen werden muss.

Über Versionierung habe ich mir bisher noch gar keine Gedanken gemacht (abgesehen davon, dass das später wahrscheinlich mal mit envers passieren wird) - mir kam an dieser Stelle gar nicht die Idee, dass eine Versionierung die Verallgemeinerung des Flags ist. Die Idee gefällt mir aber recht gut.
Allerdings werden nicht alle Entities versioniert werden, daher werde ich für die versionierten noch eine zweite Superklasse erstellen.

Die Implementierung der isNew-Methode würde ich dann entsprechend austauschen, falls Spring Data JPA intern nicht noch ein separates Interface für versionierte Entities anbietet.

*** Edit ***

Achso, meine Implementierung der equals-Methode ist Murks, weil sie falsche Ergebnisse hervorrufen kann, wenn man unterschiedliche Klassen miteinander vergleicht. Der Equals-Vergleich muss also in der Unterklasse gemacht werden.

[QUOTE=cmrudolph]
Achso, meine Implementierung der equals-Methode ist Murks, weil sie falsche Ergebnisse hervorrufen kann, wenn man unterschiedliche Klassen miteinander vergleicht. Der Equals-Vergleich muss also in der Unterklasse gemacht werden.[/QUOTE]
wenn du aber nur überall auf gleiche Klasse prüfen willst, dann muss das ja nicht in mehreren Methoden geschehen,

mit den drei Standardzeilen

        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;

ginge es auch hier

Daran hatte ich auch schon gedacht. Das hat aber zur Folge, dass Proxies sich mit der Entityklasse nicht vergleichen lassen bzw. dass diese immer ungleich sind.
Bei der Verwendung von JPA sind Proxies ja fast zwangsläufig im Spiel (sofern man nicht mit Bytecodemanipulation arbeitet, was wieder Implementierungsspezifisch ist), weshalb mir ein o instanceof EntityClass sicherer erscheint.

Da lässt sich Class#isAssignableFrom(java.lang.Class) nutzen. Damit kannst Du equals -wie von SlaterB vorgeschlagen- in der Oberklasse implementieren und gleichzeitig großzügig (nicht nur Klassenidentität) bei der equals-Kompatibilität sein.

Meinst du so:

public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o.getClass().isAssignableFrom(this.getClass()) || this.getClass().isAssignableFrom(o.getClass()))) return false;

    AbstractEarlyIdPersistable that = (AbstractEarlyIdPersistable) o;
 
    return id.equals(that.id);
}