Grails 2.4.4: Sicheres Rollback in einer komplexen Service-Methode

9

Betrachten Sie den folgenden Dienst (standardmäßig als Transaktion). Ein Spieler muss immer einen Account haben. Ein Spieler ohne mindestens ein entsprechendes Konto ist ein Fehlerzustand.

%Vor%

Ich habe dieses Muster von erfahrenen Grails-Entwicklern gesehen, und wenn ich ihnen sage, dass, wenn die Erstellung des Accounts des Players aus irgendeinem Grund fehlschlägt, es den Player nicht zurücksetzt und die DB in einem ungültigen Zustand belässt, schauen sie bei mir, als wäre ich verrückt, weil Grals den Spieler zurückrollen, weil Dienste transaktional richtig sind?

Wenn ich also ein SQL-Typ bin, suche ich nach einer Möglichkeit, Rollback in Grails aufzurufen. Es gibt keinen. Laut verschiedenen Beiträgen gibt es nur zwei Möglichkeiten, um Grails zum Rollback in einem Dienst zu zwingen:

  1. wirft eine ungeprüfte Ausnahme aus. Weißt du, was das ist?
  2. Verwenden Sie keine Service-Methoden oder Transaktionsannotationen, verwenden Sie dieses Konstrukt:

.

%Vor%

1. eine unkontrollierte Ausnahme auslösen

1.1 Daher müssen wir Runtime-Ausnahmen auf Rollback werfen. Das ist in Ordnung für mich (ich mag Ausnahmen), aber das wird nicht mit den Grails-Entwicklern gelingen, die Ausnahmen als einen Rückfall zu Java betrachten und uncool sind. Das bedeutet auch, dass wir die Art und Weise ändern müssen, wie die App derzeit ihre Service-Schicht nutzt.

1.2 Wenn eine Ausnahme ausgelöst wird, verlieren Sie die p.errors - Sie verlieren die Validierungsdetails.

1.3 Unsere neuen Grails-Entwickler kennen den Unterschied zwischen einer ungeprüften und einer geprüften Ausnahme nicht und wissen nicht, wie sie den Unterschied erkennen können. Das ist wirklich gefährlich.

1.4. Verwenden Sie .save (failOnError: true) Ich bin ein großer Fan davon, aber es ist nicht überall angemessen. Manchmal müssen Sie den Grund überprüfen, bevor Sie weitermachen, keine Ausnahme auslösen. Sind die Ausnahmen, die generiert werden können, immer aktiviert, immer deaktiviert oder beides? I.e. wird FailOnError AWLAYS Rollback, egal was die Ursache? Niemand, den ich gefragt habe, kennt die Antwort darauf, was beunruhigend ist, sie verwenden blindes Vertrauen, um korrupte / inkonsistente DBs zu vermeiden.

1.5 Was passiert, wenn ein Controller Service A aufruft, der Service B und dann Service C aufruft. Service A muss jede Ausnahme abfangen und einen schön formatierten Rückgabewert an den Controller zurückgeben. Wenn Service C eine Ausnahme auslöst, die von Service A abgefangen wird, werden Service-B-Transaktionen zurückgesetzt? Dies ist wichtig, um eine funktionierende Anwendung erstellen zu können.

UPDATE 1: Nach einigen Tests scheint es, dass jede Laufzeitausnahmebedingung, selbst wenn sie in nicht verwandten untergeordneten Aufrufen ausgelöst und abgefangen wird, dazu führt, dass alles im übergeordneten Element zurückgesetzt wird. Es ist jedoch in der Elternsitzung nicht einfach zu erkennen, dass dieses Rollback stattgefunden hat. Sie müssen sicherstellen, dass Sie beim erneuten Auftreten einer Ausnahme entweder eine Benachrichtigung zurückgeben oder eine Benachrichtigung an den Anrufer weitergeben, um zu zeigen, dass ein solcher Fehler aufgetreten ist eine Art, dass alles andere zurückgerollt wird.

2. withTransaction

2.1 Dies scheint ein Basar-Konstrukt zu sein. Wie rufe ich das auf und was gebe ich für den Parameter "status"? Was ist "setRollbackOnly" genau ? Warum wird es nicht einfach "Rollback" genannt? Was ist der "einzige" Teil? Es ist an ein Domänenobjekt gebunden, wenn Ihre Methode mehrere verschiedene Domänenobjekte aktualisieren möchte.

2.2 Wo soll man diesen Code ablegen? In mit der DomainObject-Klasse? Im Quellordner (d. H. Nicht in einem Dienst oder Controller?)? Direkt in der Steuerung? (Wir wollen Geschäftslogik in den Controllern nicht duplizieren)

3. Ideale Situation.

3.1 Der allgemeine Fall ist, dass wir alles, was wir in einer Servicemethode machen, rückgängig machen wollen, wenn irgendetwas in dieser Servicemethode aus irgendeinem Grund nicht gespeichert werden kann oder irgendeine Ausnahme aus irgendeinem Grund auslöst (aktiviert oder deaktiviert).

>

3.2 Im Idealfall möchte ich Service-Methoden "immer Rollback, es sei denn, ich explizit commit", die die sicherste Strategie ist, aber das ist nicht möglich, glaube ich.

Die Frage ist, wie erreiche ich die ideale Situation?

Wird das Aufrufen von save (failOnError: true) IMMER Rollback alles, egal was der Grund für das Scheitern? Dies ist nicht perfekt, da es für den Aufrufer nicht einfach ist zu wissen, welches Domänenobjekt das Problem verursacht hat.

Oder definieren Leute viele Ausnahmeklassen, die runtimeException von der Klasse ableiten, und fangen dann explizit jede davon im Controller ab, um die passende Antwort zu erzeugen? Dies ist der alte Java-Weg, und unsere groovigen Devs puhen diesen Ansatz aufgrund der Menge an Kesselplatten-Code, den wir schreiben müssen.

Mit welchen Methoden nutzen die Menschen das?

    
John Little 30.01.2015, 18:14
quelle

2 Antworten

3

Ich würde mich nicht als Experte bezeichnen, und diese Frage ist über ein Jahr alt, aber ich kann einige dieser Fragen beantworten, wenn auch nur für zukünftige Suchende. Ich überarbeite gerade einige Controller, um Dienste zu nutzen, um Transaktionen zu nutzen.

  

Ich habe dieses Muster von erfahrenen Grails-Entwicklern gesehen, und wenn ich ihnen sage, dass, wenn die Erstellung des Accounts des Players aus irgendeinem Grund fehlschlägt, es den Player nicht zurücksetzt und die DB in einem ungültigen Zustand belässt, schauen sie bei mir, als wäre ich verrückt, weil Grals den Spieler zurückrollen, weil Dienste transaktional richtig sind?

Ich sehe nicht in der Dokumentation, wo es explizit besagt, dass die Rückkehr von einer Service-Methode nicht Rollback der Transaktion, aber ich kann mir nicht vorstellen, dass dies ein sehr vernünftiges Verhalten wäre. Dennoch ist Testen ein einfacher Weg, um sich selbst zu beweisen.

  

1.2 Wenn eine Ausnahme ausgelöst wird, verlieren Sie die p.errors - Sie verlieren die Validierungsdetails.

Da Sie derjenige sind, der die Ausnahme auslöst, können Sie die Fehler mit hineinwerfen. Zum Beispiel:

%Vor%

Wenn Sie die Ausnahme abfangen, laden Sie die Instanz erneut (da das Auslösen einer Ausnahme die Sitzung löscht), fügen Sie die Fehler wieder in die Instanz ein und übergeben sie dann wie üblich an die Ansicht:

%Vor%

Dies wird unter der Überschrift "Validierungsfehler und Rollback" auf der Seite von Ссылка .

  

1.4. benutze .save (failOnError: true) Ich bin ein großer Fan davon, aber es ist nicht überall passend. Manchmal müssen Sie den Grund überprüfen, bevor Sie weitermachen, keine Ausnahme auslösen. Sind die Ausnahmen, die generiert werden können, immer aktiviert, immer deaktiviert oder beides? I.e. wird FailOnError AWLAYS Rollback, egal was die Ursache? Niemand, den ich gefragt habe, kennt die Antwort darauf, was beunruhigend ist, sie verwenden blindes Vertrauen, um korrupte / inkonsistente DBs zu vermeiden.

failOnError bewirkt, dass save() ein ValidationException , also ja, wenn Sie in einer Transaktion sind und diese Ausnahme nicht überprüfen, wird die Transaktion zurückgesetzt.

Im Allgemeinen scheint es nicht "Grailsy" zu sein, viel failOnError zu verwenden, wahrscheinlich aus den von Ihnen genannten Gründen (z. B. mangelnde Kontrolle). Stattdessen überprüfen Sie, ob save() fehlgeschlagen ist ( if (!save()) ... ), und ergreifen Sie Maßnahmen basierend darauf.

  
  1. withTransaction
  2.   

Ich bin mir nicht sicher, worum es geht, denn SpringSource fördert wirklich die Nutzung von Diensten für alles. Ich persönlich mag es auch nicht.

Wenn Sie einen bestimmten Service nicht-transaktional machen und dann eine Methode transaktional machen möchten, können Sie diese eine Methode einfach mit @Transactional kommentieren (es sei denn, Ihre Entwickler mögen Anmerkungen auch nicht, weil sie zu "Java" sind) ;)).

Hinweis! Sobald Sie eine einzelne Methode mit @Transactional markieren, wird der gesamte Service nicht transaktional.

  

3.1 Der allgemeine Fall besteht darin, dass wir alles, was wir in einer Servicemethode tun, rückgängig machen wollen, wenn irgendetwas in dieser Servicemethode aus irgendeinem Grund nicht gespeichert werden kann oder eine Ausnahme aus irgendeinem Grund auslöst (aktiviert oder deaktiviert).

>

Ich habe das Gefühl, dass geprüfte Ausnahmen generell nicht als "Groovy" betrachtet werden (was sie auch nicht zu Grails-y macht). Nicht sicher über den Grund dafür.

Es sieht jedoch so aus, als könnten Sie Ihrem Dienst das Rollback für Ihre geprüften Ausnahmen mitteilen, indem Sie sie in rollbackFor Option auf @Transactional .

  

Oder definieren Leute viele Ausnahmeklassen, die runtimeException von der Klasse ableiten, und fangen dann explizit jede davon im Controller ab, um die passende Antwort zu erzeugen? Dies ist der alte Java-Weg, und unsere groovigen Devs puhen diesen Ansatz aufgrund der Menge an Kesselplatten-Code, den wir schreiben müssen.

Das Schöne an Groovy ist, dass Sie Ihre Kesselplatte einmal schreiben und dann wiederholt anrufen können. Ein Muster, das ich oft gesehen habe und das ich gerade benutze, ist ungefähr so:

%Vor%

Und dann rufen Sie es in jeder Controller-Aktion wie folgt auf:

%Vor%

( withInstance ist eine weitere ähnliche Methode, die die Prüfung auf Existenz und optimistisches Sperren hochlädt.)

Dieser Ansatz hat Nachteile. Sie erhalten in jeder Aktion dieselben Umleitungen. Sie möchten wahrscheinlich einen Satz von Methoden für jeden Controller schreiben; und es scheint irgendwie albern, eine Schließung in eine Methode zu übergeben und zu erwarten, dass die Methode weiß, welche Ausnahmen die Schließung auslösen wird. Aber hey, Programmierung dreht sich alles um Kompromisse, oder?

Hoffentlich ist das zumindest interessant.

    
Charles Wood 22.03.2016 17:30
quelle
0

Wenn Sie einen Service haben wie: In einer Grails 2-App wäre es empfehlenswert, transactionStatus.setRollbackOnly() zu verwenden.

%Vor%

Siehe: Ссылка

    
Sergio del Amo 23.11.2017 17:16
quelle

Tags und Links