Integrationsmuster mit Apache Camel

Integrationsmuster mit Apache Camel

1. Überblick

Dieser Artikel behandelt einige wichtige EIPs (Enterprise Integration Patterns), 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 Fallthis article, um die Grundlagen aufzufrischen.

2. Über EIPs

Enterprise-Integrationsmuster sind Entwurfsmuster, die Lösungen für Integrationsprobleme bieten sollen. Camel bietet Implementierungen für viele dieser Muster. Die vollständige Liste der unterstützten Muster finden Sie unterthis link.

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

2. Inhaltsbasierter Router

Der inhaltsbasierte Router ist ein Nachrichtenrouter, der eine Nachricht basierend auf einem Nachrichtenkopf, einem Teil der Nutzlast oder im Grunde genommen allem aus dem Nachrichtenaustausch, den wir als Inhalt betrachten, an ihr Ziel weiterleitet.

Es beginnt mit der DSL-Anweisung vonchoice(), gefolgt von einer oder mehreren DSL-Anweisungen vonwhen(). Jedeswhen() 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 Sie eine Route definieren, die Dateien aus einem Ordner verwendet und sie je nach Dateierweiterung in zwei verschiedene Ordner verschiebt. Unsere Route wird in einer Spring-XML-Datei mit benutzerdefinierter XML-Syntax für Camel referenziert:




    

Die Routendefinition ist in der KlasseContentBasedFileRouterenthalten, in der Dateien je nach Erweiterung aus dem Quellordner in zwei verschiedene Zielordner geleitet 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 befindet sich inhere.

Danach müssen wir die Klasse vonCamelConfigurationerweitern und die Methode vonroutes()überschreiben, die aufContentBasedFileRouterverweist:

@Configuration
public class ContentBasedFileRouterConfig extends CamelConfiguration {

    @Bean
    ContentBasedFileRouter getContentBasedFileRouter() {
        return new ContentBasedFileRouter();
    }

    @Override
    public List routes() {
        return Arrays.asList(getContentBasedFileRouter());
    }
}

Die Erweiterung wird mitSimple Expression Language über die DSL-Anweisungsimple () 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 vonotherwise(), um alle Nachrichten weiterzuleiten, die die mitwhen()angegebenen Anweisungen 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 Router vonMessageTranslator, 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 DSL-Anweisung vontransform()transformieren können.

Ein Beispiel für die Verwendung eines benutzerdefinierten Prozessors finden Sie inprevious article, wo wir einen Prozessor definiert haben, der dem Dateinamen jeder eingehenden Datei einen Zeitstempel voranstellt.

Lassen Sie uns nun anhand der Anweisungtransform()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 Anweisungtransform()für jede Datei aus dem Quellordner an den Dateiinhalt an und verschieben transformierte Dateien in einen Zielordner.

4. Multicast

Multicast erlaubt unsroute the same message to a set of different endpoints and process them in a different way.

Dies ist möglich, indem Sie die DSL-Anweisung vonmulticast()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 vonparallelProcessing()geändert werden.

Camel verwendet standardmäßig die letzte Antwort als ausgehende Nachricht nach den Multicasts. Es ist jedoch möglich, eine andere Aggregationsstrategie für die Zusammenstellung der Antworten aus den Multicasts zu definieren.

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 an verschiedene Zielordner senden. Hier verwenden wirdirect: component, wodurch 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

Der Splitter ermöglicht es uns,split the incoming message into a number of pieces and processing each of them individually. zu verwenden. Dies ist möglich, indem die DSL-Anweisung vonsplit()verwendet wird.

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 Inhalt der Datei 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 ist zu erwarten, 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. Mit Dead Letter Channel können wir angeben, ob die ausgelöste Ausnahme an den Anrufer weitergegeben werden soll und wohin der fehlgeschlagene Exchange weitergeleitet werden soll.

Wenn eine Nachricht nicht zugestellt werden kann, verschiebt Dead Letter Channel (falls verwendet) die Nachricht an den Dead Letter-Endpunkt.

Lassen Sie uns dies anhand eines Beispiels demonstrieren, indem Sie eine Ausnahme auf der Route auslösen:

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!");
        });
    }
}

Hier haben wir einerrorHandlerdefiniert, das fehlgeschlagene Lieferungen protokolliert und die Strategie für die erneute Lieferung definiert. Durch Einstellen vonretryAttemptedLogLevel() wird jeder erneute Zustellversuch mit der angegebenen Protokollstufe protokolliert.

Damit dies voll funktionsfähig ist, müssen wir zusätzlich einen Logger konfigurieren.

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 feststellen 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 an einigen Beispielen 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 inover on GitHub.