C++ - Transaktion-Muster für Alles oder Nichts Arbeiten
Angenommen ich habe zwei Funktionen DoTaskA
und DoTaskB
—sowohl in der Lage zu werfen TaskException
—mit Ihren entsprechenden "rollback" - Funktionen UndoTaskA
und UndoTaskB
. Was ist das beste Muster zu verwenden, so dass entweder beide gelingen oder scheitern beide?
Die besten, die ich jetzt haben, ist
bool is_task_a_done = false,
is_task_b_done = false;
try {
DoTaskA();
is_task_a_done = true;
DoTaskB();
is_task_b_done = true;
} catch (TaskException &e) {
//Before rethrowing, undo any partial work.
if (is_task_b_done) {
UndoTaskB();
}
if (is_task_a_done) {
UndoTaskA();
}
throw;
}
Ich weiß, dass is_task_b_done
unnötig ist, aber vielleicht gut zu zeigen code-Symmetrie im Fall fügen wir eine Dritte oder eine vierte Aufgabe später auf.
Nicht wie dieser code, da der Hilfs-Boolesche Variablen. Vielleicht gibt es etwas in der neuen C++11, die ich bin mir nicht bewusst, die kann code dieses mehr schön?
- Wenn Sie im catch-block
is_task_b_done
ist immerfalse
- Ich weiß, ich habe den Kommentar, dass es nur für den code Symmetrie im Fall fügen wir eine Dritte oder vierte Aufgabe.
- Ist Ihre app mit multi-Threading? Hast du einen Blick auf RAII?
- Für Einfachheit nehmen wir an, es ist nicht multithreaded. Suchen Sie ein design-Muster hier. Wie würde ich versuchen, diese mit RAII?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Etwas RAII commit/rollback-scope guard könnte wie folgt Aussehen:
So, wir sind angenommen, wir werden immer die guard-Objekts, nachdem die Transaktion erfolgreich ist, und rufen Sie
commit
nur nach alle die Transaktionen gelungen.PS. Als Anon-Mail sagt, es ist besser, schieben Sie alle jene taskX Objekte in einem container, wenn Sie haben viele von Ihnen, wodurch der Behälter die gleiche Semantik (call-commit auf die container zu haben, die es Begehen jedes im Besitz guard-Objekt).
PPS. Im Prinzip können Sie
std::uncaught_exception
im RAII dtor anstatt explizit zu Begehen. Ich bevorzuge ausdrücklich fest, hier, weil ich denke, es ist klarer, und funktioniert auch einwandfrei, wenn Sie den Bereich verlassen früh mit einemreturn FAILURE_CODE
anstatt eine Ausnahme.std::uncaught_exception
könnte verwendet werden, um zu vermeiden, diecommit
die etwas losgelöst von der Haupt-Körper fließen, so dass es einfach zu vergessen. Wir sollten nie gemischt error-handling Paradigmen sowieso.Es ist schwer zu erreichen-Transaktion die Konsistenz in C++. Es ist eine schöne Methode beschrieben, mit der ScopeGuard Muster in der Dr. Dobb ' s journal. Die Schönheit dieses Ansatzes ist, dass diese Bereinigung in der sowohl die normalen Situationen und in der Ausnahme-Szenarien. Es nutzt die Tatsache, dass die Objekt-Destruktoren sind, der gewährleistet, rufen Sie auf jedem scope beendet und der Ausnahme-Fall ist nur ein weiterer Anwendungsbereich beenden.
Haben Sie darüber nachgedacht, CommandPattern? Befehl Pattern-Beschreibung
Sie Kapseln alle Daten, die erforderlich sind, um zu tun, was DoTaskA() tut
in ein Objekt der command-Klasse, mit dem bonus, dass Sie umkehren können
all dies, wenn nötig (also keine Notwendigkeit, einen speziellen rückgängig machen, wenn Fehler auf
execute). Command-pattern eignet sich besonders gut für den Umgang mit "alles oder nichts"
Situationen.
Wenn Sie mehrere Befehle, die bauen auf einander auf, wie Ihr Beispiel
gelesen werden können, dann sollten Sie untersuchen, Kette der Verantwortung
vielleicht eine Reaktor-Muster kann in handliches kommen (Reaktor Beschreibung hier)
dies wird invertieren die Steuerung, aber es fühlt sich natürlich und hat die
nutzen verwandeln Sie Ihr system in ein starkes Multithreading, Multi-Komponenten
design. aber es kann overkill hier, schwer zu sagen, vom Beispiel.
Der beste Weg, dies zu erreichen ist mit scope guards, im Grunde eine kleine RAII idiom, das ruft eine rollback-handler, wenn eine Ausnahme geworfen wird.
Habe ich Fragen über eine einfache Umsetzung der ScopeGuard ein bisschen her, und die Frage, die sich in eine schöne Umsetzung, ich bin mit meiner production-Projekte. Es arbeitet mit c++11 und Lambda-Ausdrücke als die rollback-Handler.
meine Quelle hat tatsächlich zwei Versionen: eine, die ruft die rollback-handler, wenn der Konstruktor-handler wirft, und eine andere, die wird nicht werfen, wenn das passiert.
überprüfen Sie die Quelle und Anwendungsbeispiele hier.
Skalierbarkeit, die Sie möchten, speichern Sie die Tatsache, dass Sie benötigen, um ein undo für eine Aufgabe in einem container. Dann, in den catch-block, die Sie gerade aufrufen, alle undo-Schritte werden aufgezeichnet, in den container.
Kann der container, zum Beispiel, enthalten, die Funktion von Objekten rückgängig zu machen, dass die Aufgabe erfolgreich abgeschlossen haben.