Integrationsmuster mit Apache Camel

1. Übersicht

Dieser Artikel behandelt einige wichtige Unternehmensintegrationsmuster (EIPs), die von Apache Camel unterstützt werden. Integrationsmuster helfen, indem sie Lösungen für standardisierte Arten der Systemintegration bereitstellen.

Wenn Sie zuerst die Grundlagen von Apache Camel durchgehen müssen, besuchen Sie auf jeden Fall diesen Artikel, um die Grundlagen aufzufrischen.

2. Über EIPs

Unternehmensintegrationsmuster sind Entwurfsmuster, die darauf abzielen, Lösungen für Integrationsherausforderungen bereitzustellen. Camel bietet Implementierungen für viele dieser Muster. Die vollständige Liste der unterstützten Muster finden Sie unter diesem Link.

In diesem Artikel werden die Integrationsmuster Content Based Router, Message Translator, Multicast, Splitter und Dead Letter Channel behandelt.

2. Inhaltsbasierter Router

Content Based Router ist ein Nachrichtenrouter, der eine Nachricht basierend auf einem Nachrichtenkopf, einem Teil der Nutzlast oder im Grunde aus dem Nachrichtenaustausch, den wir als Inhalt betrachten, an sein Ziel weiterleitet.

Es beginnt mit der DSL-Anweisung choice () , gefolgt von einer oder mehreren when () DSL-Anweisungen. Jedes when () enthält einen Prädikatausdruck, der, wenn er erfüllt ist, zur Ausführung der enthaltenen Verarbeitungsschritte führt.

Lassen Sie uns diese EIP veranschaulichen, indem wir eine Route definieren, die Dateien aus einem Ordner verbraucht und sie je nach Dateierweiterung in zwei verschiedene Ordner verschiebt. Unsere Route wird in der Spring XML-Datei mit der benutzerdefinierten XML-Syntax für Camel referenziert:

Die Routendefinition ist in der ContentBasedFileRouter- Klasse enthalten, in der Dateien je nach Erweiterung aus dem Quellordner in zwei verschiedene Zielordner weitergeleitet werden.

Alternativ könnten wir hier den Spring Java-Konfigurationsansatz verwenden, anstatt die Spring XML-Datei zu verwenden. Dazu müssen wir unserem Projekt eine zusätzliche Abhängigkeit hinzufügen:

 org.apache.camel camel-spring-javaconfig 2.18.1 

Die neueste Version des Artefakts finden Sie hier.

Danach müssen wir die CamelConfiguration- Klasse erweitern und die route () -Methode überschreiben , die auf ContentBasedFileRouter verweist :

@Configuration public class ContentBasedFileRouterConfig extends CamelConfiguration { @Bean ContentBasedFileRouter getContentBasedFileRouter() { return new ContentBasedFileRouter(); } @Override public List routes() { return Arrays.asList(getContentBasedFileRouter()); } }

Die Erweiterung wird mithilfe der Simple Expression Language über eine simple () DSL-Anweisung ausgewertet , die zur Auswertung von Ausdrücken und Prädikaten verwendet werden sollte:

public class ContentBasedFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; private static final String DESTINATION_FOLDER_TXT = "src/test/destination-folder-txt"; private static final String DESTINATION_FOLDER_OTHER = "src/test/destination-folder-other"; @Override public void configure() throws Exception { from("file://" + SOURCE_FOLDER + "?delete=true").choice() .when(simple("${file:ext} == 'txt'")) .to("file://" + DESTINATION_FOLDER_TXT).otherwise() .to("file://" + DESTINATION_FOLDER_OTHER); } }

Hier verwenden wir zusätzlich die DSL-Anweisung else (), um alle Nachrichten weiterzuleiten, die die mit when () -Anweisungen angegebenen Prädikate nicht erfüllen .

3. Nachrichtenübersetzer

Da jedes System sein eigenes Datenformat verwendet, ist es häufig erforderlich, die von einem anderen System kommende Nachricht in das vom Zielsystem unterstützte Datenformat zu übersetzen.

Camel unterstützt den MessageTranslator- Router, mit dem wir Nachrichten entweder mithilfe eines benutzerdefinierten Prozessors in der Routing-Logik, mithilfe einer bestimmten Bean zur Durchführung der Transformation oder mithilfe der transform () DSL-Anweisung transformieren können .

Ein Beispiel für die Verwendung eines benutzerdefinierten Prozessors finden Sie im vorherigen Artikel, in dem wir einen Prozessor definiert haben, der dem Dateinamen jeder eingehenden Datei einen Zeitstempel voranstellt.

Lassen Sie uns nun anhand der Anweisung transform () zeigen, wie der Nachrichtenübersetzer verwendet wird :

public class MessageTranslatorFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; private static final String DESTINATION_FOLDER = "src/test/destination-folder"; @Override public void configure() throws Exception { from("file://" + SOURCE_FOLDER + "?delete=true") .transform(body().append(header(Exchange.FILE_NAME))) .to("file://" + DESTINATION_FOLDER); } }

In diesem Beispiel hängen wir den Dateinamen über die Anweisung transform () für jede Datei aus dem Quellordner an den Dateiinhalt an und verschieben transformierte Dateien in einen Zielordner.

4. Multicast

Mit Multicast können wir dieselbe Nachricht an verschiedene Endpunkte weiterleiten und auf unterschiedliche Weise verarbeiten .

Dies ist möglich, indem Sie die Multicast () - DSL-Anweisung verwenden und dann die darin enthaltenen Endpunkte und Verarbeitungsschritte auflisten.

Standardmäßig erfolgt die Verarbeitung auf verschiedenen Endpunkten nicht parallel. Dies kann jedoch mithilfe der DSL-Anweisung parallelProcessing () geändert werden .

Camel verwendet standardmäßig die letzte Antwort als ausgehende Nachricht nach den Multicasts. Es ist jedoch möglich, eine andere Aggregationsstrategie zu definieren, die zum Zusammenstellen der Antworten aus den Multicasts verwendet werden soll.

Lassen Sie uns anhand eines Beispiels sehen, wie Multicast EIP aussieht. Wir werden Multicast-Dateien aus dem Quellordner auf zwei verschiedene Routen übertragen, wo wir ihren Inhalt transformieren und sie an verschiedene Zielordner senden. Hier verwenden wir die direct: -Komponente, mit der wir zwei Routen miteinander verbinden können:

public class MulticastFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; private static final String DESTINATION_FOLDER_WORLD = "src/test/destination-folder-world"; private static final String DESTINATION_FOLDER_HELLO = "src/test/destination-folder-hello"; @Override public void configure() throws Exception { from("file://" + SOURCE_FOLDER + "?delete=true") .multicast() .to("direct:append", "direct:prepend").end(); from("direct:append") .transform(body().append("World")) .to("file://" + DESTINATION_FOLDER_WORLD); from("direct:prepend") .transform(body().prepend("Hello")) .to("file://" + DESTINATION_FOLDER_HELLO); } }

5. Splitter

Mit dem Splitter können wir die eingehende Nachricht in mehrere Teile aufteilen und jedes einzeln verarbeiten. Dies ist mit der split () DSL-Anweisung möglich.

Im Gegensatz zu Multicast ändert Splitter die eingehende Nachricht, während Multicast sie unverändert lässt.

Um dies an einem Beispiel zu demonstrieren, definieren wir eine Route, auf der jede Zeile aus einer Datei aufgeteilt und in eine einzelne Datei umgewandelt wird, die dann in einen anderen Zielordner verschoben wird. Jede neue Datei wird mit einem Dateinamen erstellt, der dem Dateiinhalt entspricht:

public class SplitterFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; private static final String DESTINATION_FOLDER = "src/test/destination-folder"; @Override public void configure() throws Exception { from("file://" + SOURCE_FOLDER + "?delete=true") .split(body().convertToString().tokenize("\n")) .setHeader(Exchange.FILE_NAME, body()) .to("file://" + DESTINATION_FOLDER); } }

6. Dead Letter Channel

Es ist üblich und es sollte erwartet werden, dass manchmal Probleme auftreten können, z. B. Datenbank-Deadlocks, die dazu führen können, dass eine Nachricht nicht wie erwartet zugestellt wird. In bestimmten Fällen hilft es jedoch, es mit einer bestimmten Verzögerung erneut zu versuchen, und eine Nachricht wird verarbeitet.

Dead Letter Channel allows us to control what happens with a message once it fails to be delivered. Using Dead Letter Channel we can specify whether to propagate the thrown Exception to the caller and where to route the failed Exchange.

When a message fails to be delivered, Dead Letter Channel (if used) will move the message to the dead letter endpoint.

Let's demonstrate this on an example by throwing an exception on the route:

public class DeadLetterChannelFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; @Override public void configure() throws Exception { errorHandler(deadLetterChannel("log:dead?level=ERROR") .maximumRedeliveries(3).redeliveryDelay(1000) .retryAttemptedLogLevel(LoggingLevel.ERROR)); from("file://" + SOURCE_FOLDER + "?delete=true") .process(exchange -> { throw new IllegalArgumentException("Exception thrown!"); }); } }

Here we defined an errorHandler which logs failed deliveries and defines redelivery strategy. By setting retryAttemptedLogLevel(), each redelivery attempt will be logged with specified log level.

In order for this to be fully functional, we additionally need to configure a logger.

Nach dem Ausführen dieses Tests werden die folgenden Protokollanweisungen in einer Konsole angezeigt:

ERROR DeadLetterChannel:156 - Failed delivery for (MessageId: ID-ZAG0025-50922-1481340325657-0-1 on ExchangeId: ID-ZAG0025-50922-1481340325657-0-2). On delivery attempt: 0 caught: java.lang.IllegalArgumentException: Exception thrown! ERROR DeadLetterChannel:156 - Failed delivery for (MessageId: ID-ZAG0025-50922-1481340325657-0-1 on ExchangeId: ID-ZAG0025-50922-1481340325657-0-2). On delivery attempt: 1 caught: java.lang.IllegalArgumentException: Exception thrown! ERROR DeadLetterChannel:156 - Failed delivery for (MessageId: ID-ZAG0025-50922-1481340325657-0-1 on ExchangeId: ID-ZAG0025-50922-1481340325657-0-2). On delivery attempt: 2 caught: java.lang.IllegalArgumentException: Exception thrown! ERROR DeadLetterChannel:156 - Failed delivery for (MessageId: ID-ZAG0025-50922-1481340325657-0-1 on ExchangeId: ID-ZAG0025-50922-1481340325657-0-2). On delivery attempt: 3 caught: java.lang.IllegalArgumentException: Exception thrown! ERROR dead:156 - Exchange[ExchangePattern: InOnly, BodyType: org.apache.camel.component.file.GenericFile, Body: [Body is file based: GenericFile[File.txt]]]

Wie Sie sehen können, wird bei jedem erneuten Zustellungsversuch Exchange angezeigt, für das die Zustellung nicht erfolgreich war.

7. Fazit

In diesem Artikel haben wir eine Einführung in Integrationsmuster mit Apache Camel gegeben und diese anhand einiger Beispiele demonstriert.

Wir haben gezeigt, wie diese Integrationsmuster verwendet werden und warum sie für die Lösung von Integrationsproblemen von Vorteil sind.

Code aus diesem Artikel finden Sie auf GitHub.