Ich habe eine Schnittstelle für ein Service-Objekt, das in etwa wie folgt aussieht (vereinfacht dargestellt):
%Vor% ItemService
eine einzelne Implementierung und ist als Spring Bean verkabelt. Es wird vom UI-Teil unseres Projekts und vom Code, der Ajax-Anfragen behandelt, verwendet, um Item
-Objekte in unserem Datenspeicher zu erstellen und zu ändern.
Unter der Haube sendet jede Methode eine Reihe von Ereignissen, wenn sie aufgerufen wird. Die Ereignisse werden von anderen Modulen empfangen, um beispielsweise Lucene-Indizes auf dem neuesten Stand zu halten oder Nachrichten an Administratoren zu senden, damit sie wissen, dass sich etwas geändert hat. Jeder Methodenaufruf stellt im Frühjahr eine einzelne Transaktion dar (mit org.springframework.orm.hibernate3.HibernateTransactionManager
und org.springframework.transaction.interceptor.TransactionProxyFactoryBean
).
Kürzlich bestand die Notwendigkeit, eine Reihe von Methodenaufrufen in einer einzigen Transaktion zusammenzustellen . Manchmal mit mehr als einem Service. Zum Beispiel möchten wir vielleicht etwas tun wie:
%Vor% Wir haben dies getan, indem wir einen anderen Dienst erstellt haben, mit dem Sie Aufrufe von Diensten in einer einzigen Methode im übergeordneten Dienst zusammenstellen können. Lass es uns ComposingService
nennen. ComposingService
wird wie alle anderen auch von Spring verwaltet, und da Transaktionen reentrant sind, sollte dies alles funktionieren.
Es gibt jedoch ein Problem: Wenn einer dieser Vorgänge in der Transaktion fehlschlägt und die Transaktion zurückgesetzt wird, möchten wir keinerlei Ereignisse senden .
Wenn die Transaktion nach der Hälfte der Transaktion fehlschlägt, wird die Hälfte der Ereignisse von ItemService
gesendet, bevor die Transaktion rückgängig gemacht wird. Das bedeutet, dass einige Module eine Reihe von Ereignissen für Dinge erhalten, die noch nicht geschehen sind.
Wir versuchen, einen Weg zu finden, dies zu beheben, aber wir konnten an nichts Elegantes denken. Das Beste, was wir bis jetzt erreicht haben, ist so etwas (und es ist hässlich):
%Vor% In diesem geänderten ItemService werden die Ereignisse, die sofort gesendet werden, zur Liste der Ereignisse hinzugefügt, die als Argument übergeben werden. Die Liste wird von ComposingService
verwaltet, und die Ereignisse werden von ComposingService
gesendet, sobald alle Aufrufe von ItemService
und andere Dienste erfolgreich beendet wurden.
Offensichtlich besteht das Problem darin, dass wir den Vertrag für ItemService auf hässliche Weise geändert haben. Das Aufrufen von Klassen, selbst wenn es sich um Dienste handelt, sollte sich nicht um die Verwaltung von Ereignissen kümmern müssen. Aber ich bin nicht in der Lage, darüber nachzudenken, daher diese Frage.
Das sieht nach der Art von Problem aus, das wahrscheinlich vorher gelöst wurde. Hat jemand ein Problem, das ähnlich aussieht, und wenn ja, wie haben Sie es gelöst?
Zusammenfassung Ihrer Frage: Sie suchen nach einer transaktionssicheren Methode zum Senden von Nachrichten.
Transaktionssichere Nachrichten sind genau das, wofür JMS ist. Im Frühjahr gibt es auch eine gute JMS-Integration. Weitere Informationen finden Sie im JMS-Kapitel in der Frühjahrsdokumentation .
Dadurch wird sichergestellt, dass die Nachrichten gesendet werden genau dann, wenn die Transaktion festgeschrieben ist. Es hilft auch, mit Fehlern in Listenern für diese Ereignisse umzugehen.
Ein Unterschied zu Ihrer aktuellen Einrichtung besteht darin, dass diese Ereignisse asynchron behandelt werden: Ihr Dienst wird zurückkehren, bevor diese Ereignisse behandelt wurden. (JMS wird sicherstellen, dass sie schließlich verarbeitet werden, es kann so konfiguriert werden, dass es mehrmals versucht wird und wie mit Fehlern umgegangen wird, ...). Je nach Ihren Bedürfnissen kann das gut oder schlecht sein.
Alternativ, wenn JMS zu schwergewichtig für Ihren Fall ist, könnten Sie Transaktionssynchronisierung verwenden: Wenn Sie ein Ereignis senden, verwenden Sie Spring's TransactionSynchronizationManager.registerSynchronization
und senden Sie die Nachricht direkt die afterCommit()
Ihrer TransactionSynchronization
.
Sie können entweder eine neue Synchronisation pro zu sendendem Ereignis hinzufügen oder eine Synchronisation hinzufügen und verfolgen, welche Ereignisse gesendet werden, indem Sie ein Objekt mit dieser Liste an die Transaktion binden, indem Sie TransactionSynchronizationManager.bindResource
verwenden.
Ich rate Ihnen davon ab, Ihr eigenes ThreadLocal
dafür zu verwenden, weil das in manchen Fällen schief gehen würde; Zum Beispiel, wenn Sie innerhalb Ihrer Transaktion eine neue Transaktion starten ( RequiresNew
).
Unterschiede zu Ihrem aktuellen Setup:
Alternativ können Sie beforeCommit
anstelle von afterCommit
verwenden, aber dann werden Ihre Ereignisse verarbeitet (E-Mails gesendet, ...), selbst wenn die eigentliche Übertragung an die Datenbank später fehlschlägt.
Dies ist weniger robust ( weniger transaktional ) als die Verwendung von JMS, aber leichter und einfacher einzurichten und normalerweise gut genug .
Im Gegensatz zu Wouter Coekaerts verstehe ich, dass Sie nach einer Möglichkeit fragen, Benachrichtigungen zu senden, die nur dann an den Empfänger gesendet werden, wenn die Transaktion, in der sie erstellt wurden, erfolgreich war. - Sie suchen also nach etwas, das dem Transaktions-CDI- Ereignismechanismus.
Meine Idee, es zu lösen, ist so:
Um die Ereignisse weiterzuleiten oder zu löschen, würde ich zuerst den Feder-Transaktionsmechanismus betrachten. Wenn es nicht möglich ist, sie zu erweitern, könnten Sie einen AOP-Aspekt schreiben, der die Ereignisse weiterleitet, wenn eine mit @Transactional
annotierte Methode ohne (Runtime) Exception ausgeht. Wenn die Methode @Transactional
annotierte Methode jedoch mit einer Ausnahme (Runtime) beendet wurde, löschen Sie die Ereignisse aus der Liste.
Wie @Wouter eine Alternative erwähnt, ist die Verwendung von asynchronen Messaging-Techniken. Ich kann mir zwei andere Ansätze vorstellen:
ItemService
ausgelöst wurden, werden nach dem Festschreiben in der Datenbanktabelle gespeichert (so dass sie beim Rollback nicht verfügbar sind). Ein Hintergrundjob (asynchron) untersucht die Ereignisse und ruft die entsprechenden Dienste auf. Man könnte es "Selfmade JMS" nennen. Mögliche Alternative, wenn die Messaging-Infrastruktur nicht verfügbar ist. Ich schätze, eine endgültige Antwort ist nicht möglich, da sie wirklich von den Details Ihres Systems abhängt, insbesondere von allen externen Diensten, die durch die Ereignisse ausgelöst werden.
Tags und Links java spring design-patterns architecture