Blog

Garantierte Lieferung in Spring Integration

Wilfred Springer

Wilfred Springer

Aktualisiert Oktober 23, 2025
7 Minuten

Dies ist keine Tirade gegen ESB. Ich sage nicht, dass ESBs nie einen Zweck haben, und ich behaupte auch nicht, dass alles nur ein Schwindel ist. Wenn Sie nach der Lektüre dieses Beitrags den Eindruck gewonnen haben, dass ich eine Verschwörung hinter dem ESB vermute, dann möchte ich Ihnen von vornherein sagen, dass ich das ganz sicher nicht sagen wollte.

Die einzige Behauptung, die ich aufstelle, ist die, dass Sie - wenn Sie an der Umsetzung von Integrationsmustern interessiert sind - dies sicherlich mit viel weniger erreichen können. Und wie immer gilt: Weniger ist eindeutig mehr. Sie brauchen nicht immer eine monumentale Lösung, um ein Integrationsproblem zu lösen. Spring Integration und Camel sind wunderbare alternative Lösungen, die weitaus weniger aufdringlich sind. Das bedeutet nicht, dass alles rosig und sonnig ist. Es gibt sicherlich noch einiges zu wünschen übrig. Eines der Dinge, die ich bei Spring Integration vermisse, ist eine einfache Möglichkeit, eine garantierte Nachrichtenzustellung zu erreichen, und darum geht es in diesem Beitrag. Channels Eine der wichtigsten Abstraktionen von Spring Integration ist ein Channel. Betrachten Sie ihn als eine Warteschlange, die nicht notwendigerweise von einer - nun ja - Warteschlange unterstützt wird. Es ist einfach der Ort, an dem Sie eine Nachricht übergeben oder empfangen, ohne dass Sender und Empfänger notwendigerweise von der Existenz des jeweils anderen wissen. Wenn der Empfänger die Nachricht empfängt, kann diese Nachricht aus einem Puffer gezogen werden, der intern im Kanal aufbewahrt wird (wie bei einem QueueChannel), sie kann aber auch auf synchrone Weise zugestellt werden, und zwar von demselben Thread, der sie an den Kanal gesendet hat (wie bei einem DirectChannel). Aus der Sicht des Empfängers ist das alles dasselbe.

[caption id="attachment_3712" align="alignnone" width="572"]Abstraktionen des Spring-Integrationskanals Abstraktionen des Spring-Integrationskanals[/caption]

Wenn Sie mit einem Kanal arbeiten, der von einer speicherinternen BlockingQueue unterstützt wird, haben Sie ein gewisses Maß an Zuverlässigkeit. Sie wissen, dass die Nachricht mindestens einmal, höchstens einmal, zugestellt wird und dass diese Reihenfolge erhalten bleibt, abernur, wenn Ihr System niemals abgeschaltet wird. Wenn Ihr System heruntergefahren wird, entweder weil Sie es selbst anhalten oder weil unerwartete Fehler auftreten, können alle Nachrichten in diesen Kanälen als verloren betrachtet werden: Wenn das System neu gestartet wird, sind alle diese Warteschlangen leer.

[caption id="attachment_3709" align="alignnone" width="354"]Nachrichtenverlust in regulären Kanälen im Vergleich zu KahaChannel Nachrichtenverlust in regulären Kanälen im Vergleich zu KahaChannel[/caption]

Idempotenz als Rettung? Sie könnten argumentieren, dass Sie einfach alle Ihre Nachrichten wiedergeben könnten und - solange die Empfänger oder Zwischenstationen alle idempotente Empfänger sind - alles gut funktionieren wird, aber da bin ich anderer Meinung. Zunächst einmal möchten Sie nicht Ihren gesamten Nachrichtenverlauf beim Systemstart wiedergeben. Sie brauchen also wahrscheinlich einen Mechanismus, der verhindert, dass Nachrichten, die bereits die gesamte Kette der Nachrichtenbehandler durchlaufen haben, erneut gesendet werden. Wenn der idempotente Empfänger der einzige Trick ist, den Sie in petto haben, machen Sie sich selbst verdammt viel Arbeit, und das macht Ihr Leben nicht einfacher.Abgesehen davon brauchen Sie, wenn Sie eine angemessene Auswahl von Nachrichten, die in der Vergangenheit in Ihr System gelangt sind, wiedergeben wollen, immer noch die Möglichkeit, die Nachrichten am äußeren Rand Ihres Systems zuverlässig zu speichern. Selbst wenn Sie also auf verlässliche Zustellungsgarantien innerhalb Ihres Systems verzichten, werden Sie nicht um eine zuverlässige Übergabe am Rande Ihres Systems herumkommen. (Das heißt, es sei denn, Sie überzeugen Ihre Anrufer (Geschäftspartner, externe Kunden), die gesamte Buchhaltung für Sie zu übernehmen, aber ich wette, das wird Ihnen nicht gelingen). JMS als Retter? Ich habe gesagt, dass Spring Integration keine Unterstützung für eine garantierte Zustellung bietet, was eigentlich eine Lüge war. Sie können eine zuverlässige Zustellung erreichen, wenn Sie Ihre Nachrichten über JMS leiten und eine persistente Warteschlange verwenden. Es ist also machbar. Aber warum sollten Sie den Overhead der JMS-API und alles, was sich dahinter verbirgt, in Kauf nehmen, wenn Sie den PollableChannel auch direkt auf zuverlässige Weise implementieren können? Kaha DB ist die Rettung! Das habe ich also getan. Ich präsentiere Ihnen den KahaChannel. Kaha ist der Datenspeicher von ActiveMQ, der persistente Nachrichtenwarteschlangen unterstützt. Im Grunde habe ich also nur Kaha unter ActiveMQ herausgeholt und die PollableChannel-Schnittstelle darauf implementiert. Das Ergebnis ist ein nativer PollableChannel, der Nachrichten direkt im hochoptimierten Datenspeicher von ActiveMQ speichert. Und da Kaha dateibasiert ist, benötigt es keinen weiteren externen Datenspeicher. (Natürlich könnte die PollableChannel-Schnittstelle auch problemlos auf einem anderen Journalsystem wie Howl implementiert werden. Kaha scheint jedoch viel aktiver zu sein als alle anderen mir bekannten Journalsysteme). Und was nun? Die Verwendung des KahaChannel ist ziemlich einfach. Er ist ein einfacher Ersatz für jeden anderen PollableChannel. Leider fügt er sich (noch) nicht nahtlos in die Namespaces von Spring Integration ein, so dass Sie eine Instanz eines KahaChannel unter Verwendung der Vanilla Spring-Konfiguration erstellen müssen. Wenn dies also zuvor die Konfiguration Ihres Channels war:

    <si:channel id="inboxl">
        <si:queue capacity="1101"/>
    </si:channel>

Dann können Sie Ihr System nur noch ein wenig zuverlässiger machen, indem Sie das hier als Ersatz einbauen:

    <bean id="inbox" class="nl.flotsam.spring.integration.kaha.KahaChannelFactory">
        <property name="directory" value="..."/>
    </bean>

Das obige Beispiel verwendet die KahaChannelFactory zur Erstellung einer Instanz. Das ist keine Voraussetzung. Sie können sie auch direkt konstruieren, indem Sie eine Instanz von KahaChannel erstellen und die Parameter als Konstruktorargumente übergeben. Dann entspricht der Name des darunter konstruierten Containers jedoch nicht automatisch der ID der Bean. Leistung Das Ersetzen eines speicherbasierten Kanals durch einen plattenbasierten Kanal mit zuverlässigen Liefergarantien hat natürlich seinen Preis. Der Leistungsverlust ist jedoch nicht unbedingt groß. Es hängt alles von der Größe und Komplexität Ihrer Nachrichten ab. Wenn Ihre Nachrichten kaum Daten enthalten, dann ist der Nachteil ziemlich gering: 10.000 Nachrichten durch einen In-Memory-Kanal zu pumpen, dauerte auf meinem System 3,2 Sekunden. Die gleichen Nachrichten durch einen KahaChannel zu schicken, dauerte nur 4,3 Sekunden. Rechnen Sie selbst nach. Kodierung von Nachrichten Um den KahaChannel wirklich zu einem zuverlässigen Kanal zu machen, der einen Absturz übersteht, müssen die Nachrichten natürlich gespeichert werden. Leider definiert Spring Integration noch keine Abstraktion für die Kodierung und Dekodierung von Nachrichten. Die Kaha-Abstraktionen ermöglichen die Kodierung auf DataOutput und die Dekodierung von DataInput, so dass letztendlich alles auf etwas abgebildet werden muss, das diese Abstraktionen unterstützt. Anstatt zu versuchen, die ultimative Art der Kodierung und Dekodierung einer Nachricht selbst zu implementieren (falls es so etwas überhaupt gibt), vertritt der KahaChannel im Grunde den Standpunkt, dass es an Ihnen liegt, einen vernünftigen Codec bereitzustellen. Und zu diesem Zweck definiert er seine eigene Nachrichtencodec-Schnittstelle:

public interface MessageCodec<T>  {
  void encode(Nachricht<T>  value, DataOutput out) throws IOException;
  Nachricht<T>  decode(DataInput in) throws IOException;
}

Es wird jedoch auch ein eigener SimpleMessageCodec mitgeliefert, der eine lächerlich einfache Strategie für die Persistierung Ihrer Nachrichten implementiert und nur die Grundtypen int, boolean und String als Typen der Nutzdaten und Header unterstützt. Schlussfolgerungen Spring Integration selbst bietet keinen persistenten Nachrichtenkanal, der garantiert, dass Ihre Nachrichten irgendwann beim Empfänger ankommen, selbst bei einem Neustart des Systems. Wenn Sie dies jedoch weglassen, erhalten Sie eine Toolbox, die so ziemlich alles bietet, was Sie von einer vollwertigen ESB-Lösung erwarten würden, zumindest was die Unterstützung für die Implementierung von Integrationsmustern angeht. Ein JMS Message Broker könnte verwendet werden, um eine dauerhafte Nachrichtenpersistenz hinzuzufügen. Obwohl Spring Integration keine Unterstützung für persistente Nachrichtenkanäle bietet, können Sie dennoch Ihre eigenen hinzufügen. Die Implementierung eines persistenten Kanals auf der Grundlage dieser Abstraktionen erweist sich als ziemlich einfach, wenn Sie sich auf einen Datenspeicher wie Kaha verlassen. Und ein leichtgewichtiger nativer persistenter Nachrichtenkanal scheint besser in den leichtgewichtigen Ansatz von Spring Integration zu passen. In gewissem Sinne ähnelt er dem NMR in JBI-Implementierungen: Sie wissen, dass Ihre Nachrichten sicher sind, sobald sie sich darin befinden: Er mag von einem JMS Message Broker unterstützt werden, aber für den ESB-Entwickler ist er einfach eine native Komponente des Produkts.

Verfasst von

Wilfred Springer

Contact

Let’s discuss how we can support your journey.