1. 概要

この記事では、Apache Camelでサポートされているいくつかの重要なエンタープライズ統合パターン(EIP)について説明します。 統合パターンは、システムを統合する標準化された方法のソリューションを提供することで役立ちます。

最初にApacheCamelの基本を確認する必要がある場合は、この記事にアクセスして、基本をブラッシュアップしてください。

2. EIPについて

エンタープライズ統合パターンは、統合の課題に対するソリューションを提供することを目的としたデザインパターンです。 Camelは、これらのパターンの多くの実装を提供します。 サポートされているパターンの完全なリストを表示するには、このリンクにアクセスしてください。

この記事では、コンテンツベースのルーター、メッセージトランスレーター、マルチキャスト、スプリッター、およびデッドレターチャネルの統合パターンについて説明します。

2. コンテンツベースのルーター

コンテンツベースのルーターは、メッセージヘッダー、ペイロードの一部、または基本的にコンテンツと見なされるメッセージ交換からのすべてに基づいて、メッセージを宛先にルーティングするメッセージルーターです。

choice() DSLステートメントで始まり、その後に1つ以上の when()DSLステートメントが続きます。 各when()には述語式が含まれており、これが満たされると、含まれている処理ステップが実行されます。

1つのフォルダーからファイルを消費し、ファイル拡張子に応じて2つの異なるフォルダーにファイルを移動するルートを定義することにより、このEIPを説明しましょう。 私たちのルートは、CamelのカスタムXML構文を使用してSpringXMLファイルで参照されます。

<bean id="contentBasedFileRouter" 
  class="com.baeldung.camel.file.ContentBasedFileRouter" />

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <routeBuilder ref="contentBasedFileRouter" />
</camelContext>

ルート定義はContentBasedFileRouterクラスに含まれており、ファイルは拡張子に応じてソースフォルダーから2つの異なる宛先フォルダーにルーティングされます。

または、Spring XMLファイルを使用するのではなく、ここでSpringJava構成アプローチを使用することもできます。 そのためには、プロジェクトに依存関係を追加する必要があります。

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-spring-javaconfig</artifactId>
    <version>2.18.1</version>
</dependency>

アーティファクトの最新バージョンはここにあります。

その後、 CamelConfiguration クラスを拡張し、 ContentBasedFileRouterを参照するroutes()メソッドをオーバーライドする必要があります。

@Configuration
public class ContentBasedFileRouterConfig extends CamelConfiguration {

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

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

拡張機能は、式と述語の評価に使用することを目的とした simple ()DSLステートメントを介して Simple ExpressionLanguageを使用して評価されます。

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

ここでは、 when()ステートメントで指定された述語を満たさないすべてのメッセージをルーティングするために、 otherswise()DSLステートメントを追加で使用しています。

3. メッセージ翻訳者

すべてのシステムが独自のデータ形式を使用するため、別のシステムからのメッセージを宛先システムでサポートされているデータ形式に変換する必要があることがよくあります。

CamelはMessageTranslatorルーターをサポートしており、ルーティングロジックのカスタムプロセッサを使用するか、特定のBeanを使用して変換を実行するか、 transform()DSLステートメントを使用してメッセージを変換できます。

カスタムプロセッサを使用した例は、前の記事にあります。ここでは、各受信ファイルのファイル名にタイムスタンプを付加するプロセッサを定義しました。

次に、 transform()ステートメントを使用してMessageTranslatorを使用する方法を示します。

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

この例では、ソースフォルダーから各ファイルの transform()ステートメントを介してファイルの内容にファイル名を追加し、変換されたファイルを宛先フォルダーに移動しています。

4. マルチキャスト

マルチキャストを使用すると、同じメッセージを一連の異なるエンドポイントにルーティングし、それらを異なる方法で処理することができます

これは、 multicast() DSLステートメントを使用し、エンドポイントとその中の処理ステップを一覧表示することで可能になります。

デフォルトでは、異なるエンドポイントでの処理は並行して実行されませんが、これは parallelProcessing()DSLステートメントを使用して変更できます。

Camelは、デフォルトで、マルチキャスト後の送信メッセージとして最後の応答を使用します。 ただし、マルチキャストからの応答をアセンブルするために使用される別のアグリゲーション戦略を定義することは可能です。

例でマルチキャストEIPがどのように見えるかを見てみましょう。 ソースフォルダーから2つの異なるルートにファイルをマルチキャストし、そこでコンテンツを変換して、異なる宛先フォルダーに送信します。 ここでは、 direct:component を使用して、2つのルートをリンクできるようにします。

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. スプリッター

スプリッターを使用すると、着信メッセージをいくつかの部分に分割し、それぞれを個別に処理できます。これは、 split()DSLステートメントを使用することで可能になります。

マルチキャストとは対照的に、スプリッターは着信メッセージを変更しますが、マルチキャストはそのままにします。

これを例で示すために、ファイルの各行が分割されて個々のファイルに変換され、別の宛先フォルダーに移動されるルートを定義します。 新しいファイルはそれぞれ、ファイルの内容と同じファイル名で作成されます。

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を使用すると、メッセージの配信に失敗した場合のメッセージの処理を制御できます。 Dead Letter Channelを使用すると、スローされた例外を呼び出し元に伝播するかどうか、および失敗したExchangeをどこにルーティングするかを指定できます。

メッセージの配信に失敗すると、Dead Letter Channel(使用されている場合)はメッセージをDeadLetterエンドポイントに移動します。

ルート上で例外をスローすることにより、例でこれを示しましょう。

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

ここでは、失敗した配信をログに記録し、再配信戦略を定義するerrorHandlerを定義しました。 retryAttemptedLogLevel()を設定すると、各再配信の試行が指定されたログレベルでログに記録されます。

これを完全に機能させるには、ロガーを追加で構成する必要があります。

このテストを実行すると、次のログステートメントがコンソールに表示されます。

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]]]

お気づきのとおり、再配信の試行はそれぞれログに記録され、配信が成功しなかったExchangeが表示されます。

7. 結論

この記事では、Apache Camelを使用した統合パターンの概要を示し、いくつかの例でそれらを示しました。

これらの統合パターンの使用方法と、統合の課題を解決するためにそれらが有益である理由を示しました。

この記事のコードは、GitHubにあります。