Sicherstellung der Transaktionskonsistenz mit DDD

9

Ich beginne mit DDD und verstehe, dass aggregierte Wurzeln verwendet werden, um die transnationale Konsistenz sicherzustellen. Wir sollten nicht mehrere Aggregate in einem Anwendungsdienst ändern.

Ich würde jedoch gerne wissen, wie ich mit der folgenden Situation umgehen soll.

Ich habe eine aggregierte Wurzel namens Produkte.

Es gibt auch eine aggregierte Wurzel namens Group.

Beide haben IDs und können unabhängig voneinander bearbeitet werden.

Mehrere Produkte können auf dieselbe Gruppe verweisen.

Ich habe einen Anwendungsdienst, der die Gruppe eines Produkts ändern kann:

ProductService.ChangeProductGroup(string productId, string groupId)

  1. Prüfgruppe existiert
  2. Produkt aus dem Repository holen
  3. Stellen Sie die Gruppe ein
  4. Schreiben Sie das Produkt zurück in das Repository

Ich habe auch einen Anwendungsdienst, bei dem die Gruppe gelöscht werden kann:

GroupService.DeleteGroup(string groupId) 1. Rufen Sie Produkte aus dem Repository ab, dessen Gruppen-ID auf die angegebene Gruppen-ID festgelegt ist, stellen Sie sicher, dass die Anzahl 0 ist, oder brechen Sie ab. 2. Löschen Sie die Gruppe aus dem Gruppen-Repository. 3. Speichern Sie die Änderungen

Meine Frage ist das folgende Szenario, was würde passieren, wenn:

In der ProductService.ChangeProductGroup überprüfen wir, ob die Gruppe vorhanden ist (dies ist der Fall). Unmittelbar nach dieser Überprüfung löscht ein separater Benutzer die productGroup (über die andere GroupService.DeleteGroup). In diesem Fall setzen wir einen Verweis auf ein Produkt, das gerade gelöscht wurde?

Ist dies ein Fehler in meinem Design, da ich ein anderes Domain-Design verwenden sollte (ggf. zusätzliche Elemente hinzufügen), oder müsste ich Transaktionen verwenden?

g18c
quelle

Antworten:

2

Wir haben das gleiche Problem.

Und ich sehe keine Möglichkeit, dieses Problem zu lösen, sondern mit Transaktionen oder mit Konsistenzprüfung in der DB, um im schlimmsten Fall eine Ausnahme zu bekommen.

Sie können auch eine pessimistische Sperre verwenden, um das Gesamtstammverzeichnis oder nur seine Teile für andere Clients zu blockieren, bis der Geschäftsvorgang abgeschlossen ist, was in gewissem Maße einer serialisierbaren Transaktion entspricht.

Die Art und Weise, wie Sie vorgehen, hängt stark von Ihrem System und Ihrer Geschäftslogik ab.
Parallelität ist überhaupt keine leichte Aufgabe.
Selbst wenn Sie ein Problem erkennen können, wie können Sie es beheben? Einfach den Vorgang abbrechen oder dem Benutzer erlauben, Änderungen zusammenzuführen?

Wir verwenden Entity Framework und EF6 verwendet standardmäßig read_commited_snapshot, sodass zwei aufeinanderfolgende Lesevorgänge aus dem Repository zu inkonsistenten Daten führen können. Wir denken nur an die Zukunft, wenn die Geschäftsprozesse klarer umrissen werden und wir Infromed-Entscheidungen treffen können. Und ja, wir überprüfen immer noch die Konsistenz auf Modellebene, so wie Sie es tun. Dies ermöglicht es zumindest, BL getrennt von DB zu testen.

Ich empfehle Ihnen auch, über die Konsistenz des Repositorys nachzudenken, falls Sie "lange" Geschäftstransaktionen haben. Es ist ziemlich schwierig, wie sich herausstellte.

Pavel Voronin
quelle
1

Ich denke, dies ist keine domänengesteuerte Design-spezifische Frage, sondern eine Frage der Ressourcenverwaltung.

Ressourcen müssen beim Erwerb einfach gesperrt werden .

Dies gilt, wenn der Anwendungsdienst im Voraus weiß, dass die erfasste Ressource ( Groupin Ihrem Beispiel das Aggregat) geändert wird. In Domain-Driven Design ist das Sperren von Ressourcen ein Aspekt, der zum Repository gehört.

Rucamzu
quelle
Nein, das Sperren ist kein "Muss". Tatsächlich ist es eine ziemlich schreckliche Sache, mit lang laufenden Transaktionen zu tun. Sie sind besser dran, wenn Sie eine optimistische Parallelität verwenden, die jedes moderne ORM sofort unterstützt. Das Tolle an optimistischer Parallelität ist, dass sie auch mit nicht transaktionalen Datenbanken funktioniert, sofern sie atomare Updates unterstützen.
Aaronaught
1
Ich stimme den Nachteilen des Sperren in lang laufenden Transaktionen zu, aber das von @ g18c beschriebene Szenario deutet auf das Gegenteil hin, dh kurze Operationen über einzelne Aggregate.
Rucamzu
0

Warum speichern Sie die Produkt-IDs nicht in der Gruppenentität? Auf diese Weise haben Sie es nur mit einem einzigen Aggregat zu tun, was die Sache einfacher macht.

Sie müssen dann eine Art Parallelitätsmuster implementieren, z. B. Wenn Sie eine optimistische Parallelität wählen, fügen Sie der Gruppenentität einfach eine Versionseigenschaft hinzu und lösen Sie eine Ausnahme aus, wenn die Versionen beim Aktualisieren nicht übereinstimmen, d. H.

Produkt zu einer Gruppe hinzufügen

  1. Überprüfen Sie, ob das Produkt vorhanden ist
  2. Gruppe aus dem Repository abrufen (einschließlich der Version-ID-Eigenschaft)
  3. Group.Add (ProductId)
  4. Gruppe mit Repository speichern (Update mit neuer Versions-ID und Hinzufügen einer where-Klausel, um sicherzustellen, dass die Version nicht geändert wurde.)

In vielen ORMs ist eine optimistische Parallelität mithilfe von Versions-IDs oder Zeitstempeln integriert, aber es ist einfach, eigene Rollen zu erstellen. Hier ist ein guter Beitrag darüber, wie es in Nhibernate gemacht wird: http://ayende.com/blog/3946/nhibernate-mapping-concurrency .

Scott Millett
quelle