User Tools

Site Tools


technology:reservationpattern

Reservation Pattern

Das Reservation Pattern ist relativ einfach. Man reserviert etwas für eine bestimmte Zeit, innerhalb der man die Reservierung bestätigen oder stornieren kann, in dem Wissen, dass das Bestätigen innerhalb des Reservierungszeitraums funktionieren wird.

Man kann das Reservierungsmuster im Grunde wie das 2PC1) einer verteilten Transaktion verwenden. Nur ist der Sprachgebrauch nicht technisch, sondern Domänen bezogen. Wenn man also davon spricht, einen Mietwagen zu reservieren, zusammen mit einem Hotelzimmer und einem Flug, dann verwendet man die Sprache der Domäne der Reisevermittlung. Man spricht nicht davon, dass eine verteilte Transaktion einen Mietwagen, ein Hotelzimmer und einen Flug speichert, das aber in einer noch nicht committed, sondern die Transaktion auch jederzeit wieder abbrechen kann.

Genauso kann man davon sprechen, dass man in der Auftrags-Registratur für eine Auftrags-ID eine Auftragsnummer reserviert. Sobald die Auftragsnummer für die ID verwendet wurde, kann man die Reservierung bestätigen. Wenn sie nicht verwendet wurde, kann sie storniert werden, wodurch man die Auftragsnummer für neue Aufträge wieder frei gibt.

Ob man so Auftragsnummern vergeben möchte ist Aufgabe der Analysephase. Auf jeden Fall kann man das immer so als Fallback oder Schnellschuss lösen, in dem Wissen, dass man so die Auftragsnummernvergabe so gebaut hat, dass der Auftrag sie mit steuern kann2), und dass die Registratur die Möglichkeit hat, die Auftragsnummer eindeutig zu halten.

Dafür ist immer ein Prozess nötig, d.h. man kann nicht einfach einen Befehl an ein Aggregate schicken, sondern es muss ein Prozess angestoßen werden, der die einzelnen Schritte koordiniert. Dabei ist es absolut notwendig, dass der Auftrag und die Registratur die entsprechenden Methodenaufrufe nur von dem Prozess akzeptieren. Ansonsten könnten einzelne Befehle an den Auftrag zur Auftragsnummernänderung gesendet werden, ohne entsprechende Befehle an die Registratur. Das würde zu einer Inkonsistenz führen, weil dann die Nummern in den Aufträgen und die in der Registratur abweichen, was letztendlich dazu führt, dass die Eindeutigkeit der Auftragsnummern nicht mehr garantiert werden kann.

Sperren von Entitäten

In dem Artikel Aggregate wird beschrieben, wie man top-down und bottom-up Prozesse bauen kann. Ich habe mich für bottom-up ausgesprochen, weil es keine Prozesse geben muss, die Inkonsistenzen korrigieren, die der Anwender u.U. unwissentlich erzeugt hat, sondern der Anwender durch die einzelnen Prozessschritte geleitet wird, um eventuelle Änderungen nachträglich vornehmen zu können.

Als Beispiel kann man da den abgerechneten Zeitdatensatz nehmen. In PRO•M ist es so, dass ein Zeitdatensatz, wenn einmal abgerechnet, nicht mehr verändert werden kann, unabhängig vom Status. Man muss die Rechnung erst stornieren und dadurch die abgerechneten Zeiten wieder frei geben, um sie ändern zu können. Ein für meine Kunden nachvollziehbarerer Prozess.

Natürlich könnte man auch den Anwender seine Zeiten einfach ändern lassen, und dann einen Prozess einrichten, der dann eine Gutschrift plus neue Rechnung für die geänderten Zeiten erstellt. Das ist aber wesentlich komplizierter als der vorhandene Prozess. Fehler verhindern ist immer einfacher als nachträglich aufräumen zu müssen. Ich verhinder lieber, dass mein Haus in die Luft fliegt, als es neu aufbauen zu müssen.

Aktuell wird in den Zeitdatensatz im Rechnungslauf innerhalb der Transaktion ein Kennzeichen gesetzt, dass er abgerechnet ist. Wenn der Zeitdatensatz danach verändert werden soll, prüft er auf dieses Kennzeichen und lässt eine Änderung nicht mehr zu. Der Mechanismus klappt seit über 10 Jahren.

Da PRO•M im Hinblick auf eine Partitionstoleranz umgebaut werden soll, kann sich nicht mehr auf Transaktionen verlassen werden, auch nicht auf verteilte Transaktionen. Dafür wird das Reservierungsmuster verwendet3). Wenn ich z.B. 100 Zeitdatensätze abrechnen möchte, die ich mir gerade angucke, starte ich einen Prozess, der folgendes tut:

  1. Sperre jeden der 100 Zeitdatensätze für eine Abrechnung4). Dabei kann die Versionssnummer, die gesperrt werden soll, übergeben werden, damit auch die Version abgerechnet wird, die der Anwender im Formular sieht,
  2. Rechne die 100 Zeitdatensätze ab,
  3. Wenn irgend ein Prozessschritt fehlschlägt, entferne die Sperren in den bereits gesperrten Zeitdatensätzen.

Jetzt muss nur noch der Begriff Sperren definiert werden. Aktuell5) gibt es keine allgemeinen Sperren, sondern die bereits erwähnte explizite Sperre weil der Zeitdatensatz abgerechnet wurde. Es spricht aber nichts dagegen, allgemeine Sperren in Aggregates einzubauen. Man muss in die Sperre lediglich eine Referenz auf das dafür verantwortliche Aggregate schreiben, z.B. { Type=Invoice; Id=1234-ABCD-5678-EFGH }. Ein Übersetzer kann im UI daraus eine entsprechende Information machen, z.B. “Gesperrt weil abgerechnet”.

Damit die Konsistenz wirklich sicher gestellt ist, dürfen nur ganz bestimmte Prozesse autorisiert sein, Sperren zu schreiben. Da die Entitäten verteilt liegen können, muss das über die Authentifizierung laufen. Ein Prozess muss sich somit über seine Anmeldedaten eindeutig zu erkennen geben, und die Zugriffskontrolle muss darauf prüfen. Ansonsten könnte jeder beliebige Client Sperren in Entitäten löschen, was dazu führen könnte, dass sie die Konsistenz des Systems nicht mehr schützen.

Die Entität muss Sperren natürlich respektieren. Es muss klar definiert sein, was die Sperre bedeutet. Z.B. dass ein Zeitdatensatz nicht mehr verändert werden darf - sein Status jedoch schon! Bedeutet es auch, dass er kein (weiteres) mal abgerechnet werden darf? Nicht, wenn die Sperre aus einem anderen Grund gesetzt wurde. D.h., dass beim Abrechnen noch eine Rolle zum Zeitdatensatz geschrieben werden muss, in die rein geschrieben werden kann, ob der Zeitdatensatz abgerechnet wurde oder nicht. Diese Rolle kann in einem ganz anderen Kontext liegen, hat mit der Sperre an sich gar nichts zu tun, das sind zwei unterschiedliche Aspekte.

CAP

Das Reservierungsmuster ist dafür da, das C6) im CAP zu erhöhen, wodurch das A7) reduziert wird. Interessanterweise bleibt das P8) dadurch unverändert hoch, obwohl gerades das P wohl niemals Relevanz haben wird. Die Sache ist aber die, dass ich das Reservierungsmuster durch in-memory Event-Handler umsetzen kann, die Sagas simulieren. Dadurch kann in einer nicht-verteilten Umgebung C hoch gehalten werden, gemeinsam mit A, weil P nicht gegeben ist. Erst beim Partitionieren wird A reduziert, indem aus den in-memory Eveng-Handlern echte Sagas werden.

D.h., das ich einen Weg gefunden habe, C und A hoch zu halten solange das System nicht partitioniert wird. Wird es doch partitioniert, müssen die Prozesse nicht verändert werden9), und da ich nicht auf die Konsistenz verzichten will, muss das Antwortzeitverhalten halt ein wenig leiden.

1) “Two Phase Commit”
2) z.B. kann der Auftrag eine Änderung ablehnen wenn er schon freigegeben ist
3) Also im Grunde geschummelt, da es sich hier ja auch um eine Art von verteilter Transaktion handelt
4) dazu gleich mehr
5) PRO•M Delphi
6) Consistency
7) Availability
8) Partitionstoleranz
9) Lediglich anders umgesetzt
technology/reservationpattern.txt · Last modified: 2013/03/06 16:08 by rtavassoli