Spring統合でサブフローを使用する

Spring Integrationでのサブフローの使用

1. 概要

Spring Integrationを使用すると、いくつかのEnterprise Integration Patternsを簡単に使用できます。 これらの方法の1つは、its DSLを使用する方法です。

このチュートリアルでは、構成の一部を簡素化するためのサブフローに対するDSLのサポートについて説明します。

2. 私たちの仕事

3つの異なるバケットに分割する整数のシーケンスがあるとします。

Spring Integrationを使用してこれを行うには、3つの出力チャネルを作成することから始めます。

  • 0、3、6、9などの数字はmultipleOfThreeChannelになります

  • 1、4、7、10などの数値はremainderIsOneChannelになります

  • また、2、5、8、11などの数値はremainderIsTwoChannelになります

サブフローがどれほど役立つかを確認するために、サブフローなしでこれがどのように見えるかから始めましょう。

次に、サブフローを使用して、次のように構成を簡素化します。

  • publishSubscribeChannel

  • routeToRecipients

  • Filters、if-thenロジックを構成します

  • Routers、switchロジックを構成します

3. 前提条件

サブフローを構成する前に、これらの出力チャネルを作成しましょう。

デモが少し簡単なので、これらのQueueChannelsを作成します。

@EnableIntegration
@IntegrationComponentScan
public class SubflowsConfiguration {

    @Bean
    QueueChannel multipleOfThreeChannel() {
        return new QueueChannel();
    }

    @Bean
    QueueChannel remainderIsOneChannel() {
        return new QueueChannel();
    }

    @Bean
    QueueChannel remainderIsTwoChannel() {
        return new QueueChannel();
    }

    boolean isMultipleOfThree(Integer number) {
       return number % 3 == 0;
    }

    boolean isRemainderIOne(Integer number) {
        return number % 3 == 1;
    }

    boolean isRemainderTwo(Integer number) {
        return number % 3 == 2;
    }
}

最終的に、これらは私たちのグループ化された数が終わるところです。

Spring Integrationは簡単に複雑に見え始める可能性があるため、読みやすくするためにいくつかのヘルパーメソッドを追加します。

4. サブフローなしで解決

次に、フローを定義する必要があります。

サブフローを使用しない場合、単純な考え方は、各タイプの番号に1つずつ、3つの個別の統合フローを定義することです。

同じシーケンスのメッセージを各IntegrationFlowコンポーネントに送信しますが、各コンポーネントの出力メッセージは異なります。

4.1. IntegrationFlowコンポーネントの定義

まず、SubflowConfiguration classで各IntegrationFlowbeanを定義しましょう。

@Bean
public IntegrationFlow multipleOfThreeFlow() {
    return flow -> flow.split()
      . filter(this::isMultipleOfThree)
      .channel("multipleOfThreeChannel");
}

フローには2つのエンドポイントが含まれています–Splitter の後にFilterが続きます。

フィルターは、それがどのように聞こえるかを行います。 しかし、なぜスプリッターも必要なのでしょうか? これはすぐにわかりますが、基本的には、入力Collectionを個々のメッセージに分割します。

もちろん、同じ方法でさらに2つのIntegrationFlowBeanを定義することもできます。

4.2. メッセージングゲートウェイ

フローごとに、Message Gatewayも必要です。

簡単に言うと、これらは、RESTサービスがHTTPを抽象化する方法と同様に、Spring Integration Messages APIを呼び出し元から抽象化します。

@MessagingGateway
public interface NumbersClassifier {

    @Gateway(requestChannel = "multipleOfThreeFlow.input")
    void multipleOfThree(Collection numbers);

    @Gateway(requestChannel = "remainderIsOneFlow.input")
    void remainderIsOne(Collection numbers);

    @Gateway(requestChannel = "remainderIsTwoFlow.input")
    void remainderIsTwo(Collection numbers);

}

それぞれについて、@Gateway annotationを使用し、入力チャネルの暗黙的な名前を指定する必要があります。これは、単にBeanの名前の後に“.input”が続く名前です。 Note that we can use this convention because we are using lambda-based flows.

これらのメソッドは、フローへのエントリポイントです。

4.3. メッセージの送信と出力の確認

そして今、テストしましょう:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SeparateFlowsConfiguration.class })
public class SeparateFlowsUnitTest {

    @Autowired
    private QueueChannel multipleOfThreeChannel;

    @Autowired
    private NumbersClassifier numbersClassifier;
    @Test
    public void whenSendMessagesToMultipleOf3Flow_thenOutputMultiplesOf3() {
        numbersClassifier.multipleOfThree(Arrays.asList(1, 2, 3, 4, 5, 6));
        Message outMessage = multipleOfThreeChannel.receive(0);
        assertEquals(outMessage.getPayload(), 3);
        outMessage = multipleOfThreeChannel.receive(0);
        assertEquals(outMessage.getPayload(), 6);
        outMessage = multipleOfThreeChannel.receive(0);
        assertNull(outMessage);
    }
}

メッセージをListとして送信したことに注意してください。そのため、単一の「リストメッセージ」を取得し、それを複数の「番号メッセージ」に変換するためにスプリッターが必要でした。

receiveoと呼び出して、待機せずに次に利用可能なメッセージを取得します。 リストには3の倍数が2つあるので、2回呼び出すことができると予想されます。 receive への3回目の呼び出しは、nullを返します。

もちろん、receive, Messageを返すため、getPayloadを呼び出して番号を抽出します。

同様に、他の2つについても同じことができます。

So, that was the solution without subflows.維持する3つの個別のフローと、3つの個別のゲートウェイメソッドがあります。

ここで行うことは、3つのIntegrationFlow beanを1つのBeanに置き換え、3つのゲートウェイメソッドを1つのBeanに置き換えることです。

5. publishSubscribeChannelの使用

publishSubscribeChannel()メソッドは、サブスクライブしているすべてのサブフローにメッセージをブロードキャストします。 これにより、3つではなく1つのフローを作成できます。

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        .publishSubscribeChannel(subscription ->
           subscription
             .subscribe(subflow -> subflow
               . filter(this::isMultipleOfThree)
               .channel("multipleOfThreeChannel"))
             .subscribe(subflow -> subflow
                . filter(this::isRemainderOne)
                .channel("remainderIsOneChannel"))
             .subscribe(subflow -> subflow
                . filter(this::isRemainderTwo)
                .channel("remainderIsTwoChannel")));
}

このように、the subflows are anonymous, meaning that they can’t be independently addressed.

フローが1つしかないので、NumbersClassifier も編集しましょう。

@Gateway(requestChannel = "classify.input")
void classify(Collection numbers);

さて、since we have only one IntegrationFlow bean and one gateway method, we need only send our list once:

@Test
public void whenSendMessagesToFlow_thenNumbersAreClassified() {
    numbersClassifier.classify(Arrays.asList(1, 2, 3, 4, 5, 6));

    // same assertions as before
}

今後、統合フローの定義のみが変更されるため、テストを再度表示することはありません。

6. routeToRecipientsの使用

同じことを実現する別の方法はrouteToRecipientsです。これは、フィルタリングが組み込まれているので便利です。

この方法を使用すると、we can specify both channels and subflows for broadcasting. 

6.1. recipient

以下のコードでは、条件に基づいて、受信者としてmultipleof3ChannelremainderIs1Channel,、およびremainderIsTwoChannelを指定します。

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        .routeToRecipients(route -> route
          . recipient("multipleOfThreeChannel",
            this::isMultipleOfThree)
          . recipient("remainderIsOneChannel",
            this::isRemainderOne)
          . recipient("remainderIsTwoChannel",
            this::isRemainderTwo));
}

条件なしでrecipient を呼び出すこともでき、routeToRecipients は無条件でその宛先に公開されます。

6.2. recipientFlow

また、routeToRecipientsを使用すると、publishSubscribeChannel. と同様に、完全なフローを定義できることに注意してください。

上記のコードを変更して、anonymous subflow as the first recipientを指定しましょう。

.routeToRecipients(route -> route
  .recipientFlow(subflow -> subflow
      . filter(this::isMultipleOfThree)
      .channel("mutipleOfThreeChannel"))
  ...);

This subflow will receive the entire sequence of messages,なので、同じ動作を得るには、前と同じようにフィルタリングする必要があります。

繰り返しますが、1つのIntegrationFlowBeanで十分でした。

それでは、if-elseコンポーネントに移りましょう。 それらの1つはFilterです。

7. if-thenフローの使用

これまでのすべての例では、すでにFilterを使用しています。 幸いなことに、さらに処理するための条件だけでなく、a channel or aflow for the discarded messagesも指定できます。

廃棄フローとチャネルは、else blockのように考えることができます。

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        . filter(this::isMultipleOfThree,
           notMultiple -> notMultiple
             .discardFlow(oneflow -> oneflow
               . filter(this::isRemainderOne,
                 twoflow -> twoflow
                   .discardChannel("remainderIsTwoChannel"))
               .channel("remainderIsOneChannel"))
        .channel("multipleofThreeChannel");
}

この場合、if-elseルーティングロジックを実装しました。

  • Ifの数は3の倍数ではなく、thenはそれらのメッセージを破棄フローに破棄します。 we use a flow here since there is more logic needed to know its destination channel.

  • 破棄フローでは、ifの数は残りの1ではなく、thenはそれらのメッセージを破棄チャネルに破棄します。

8. 計算値のswitch-ing

最後に、route メソッドを試してみましょう。これにより、routeToRecipients. よりも少し細かく制御できます。Routerはフローを任意の数の部分に分割できるのに対し、Filter canは便利です。 2つだけ行います。

8.1. channelMapping

IntegrationFlowBeanを定義しましょう。

@Bean
public IntegrationFlow classify() {
    return classify -> classify.split()
      . route(number -> number % 3,
        mapping -> mapping
         .channelMapping(0, "multipleOfThreeChannel")
         .channelMapping(1, "remainderIsOneChannel")
         .channelMapping(2, "remainderIsTwoChannel"));
}

上記のコードでは、除算を実行してルーティングキーを計算します。

route(p -> p % 3,...

このキーに基づいて、メッセージをルーティングします。

channelMapping(0, "multipleof3Channel")

8.2. subFlowMapping

これで、他の場合と同様に、サブフローを指定して、channelMappingsubFlowMappingに置き換えることで、より詳細に制御できるようになりました。

.subFlowMapping(1, subflow -> subflow.channel("remainderIsOneChannel"))

または、channel methodの代わりにhandle methodを呼び出すことで、さらに制御します。

.subFlowMapping(2, subflow -> subflow
  . handle((payload, headers) -> {
      // do extra work on the payload
     return payload;
  }))).channel("remainderIsTwoChannel");

この場合、サブフローはroute()メソッドの後にメインフローに戻るため、チャネルremainderIsTwoChannel.を指定する必要があります。

9. 結論

このチュートリアルでは、サブフローを使用していくつかの方法でメッセージをフィルタリングおよびルーティングする方法について説明しました。

いつものように、完全なソースコードはon GitHubで入手できます。