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として送信したことに注意してください。そのため、単一の「リストメッセージ」を取得し、それを複数の「番号メッセージ」に変換するためにスプリッターが必要でした。
receiveをoと呼び出して、待機せずに次に利用可能なメッセージを取得します。 リストには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
以下のコードでは、条件に基づいて、受信者としてmultipleof3Channel、remainderIs1Channel,、および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
これで、他の場合と同様に、サブフローを指定して、channelMappingをsubFlowMappingに置き換えることで、より詳細に制御できるようになりました。
.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で入手できます。