EIP / Selective Consumer


Selective Consumer

一言要約

Context

Messagingを使っているアプリケーションがある。このアプリはメッセージチャネルからメッセージを取得しているが、別にチャネル上のメッセージを全部処理する必要はなくて、一部だけ処理すればそれでいい。

Problem

メッセージコンシューマーが、自分がどのメッセージを受信したいのかを選べるようにするには?

Force

通常は、コンシューマーがメッセージを選り好みすることはない。やってきたメッセージを淡々と処理するだけ。

コンシューマーが、チャネル上のすべてのメッセージを受け取りたいと思っている場合は特に問題はない。たいていの場合はそうだろう。でも、特定のメッセージだけを受け取りたいとなると、問題になる。普通のコンシューマーには、そんな手段がないからだ。

特定のメッセージだけを受け取りたくなる例として、ローンのリクエストを受け取るアプリを考えてみる。10万ドル未満の小規模ローンと10万ドル以上の大規模ローンで、それぞれその後の処理の流れを変えたいなどといった場合、小規模用のコンシューマーと大規模用のコンシューマーを用意することになるだろう。リクエストメッセージを適切なコンシューマーに届けるにはどうすればいいだろう。

一番シンプルなのは「何も考えずに、とりあえず来たメッセージを受け取る」→「もし自分が処理すべきメッセージじゃないものだった場合は、なんとかして正しいコンシューマーに渡す」という流れ。でも……

  • コンシューマーどうしは、お互いのことを知らないことが多い
  • 適切な空きチャネルをどうやって探すの?

など、いろいろ考えなければいけなくなる。「なんとかして正しいコンシューマーに渡す」のはあきらめて、とりあえずチャネルにメッセージを差し戻すっていう手もあるけど、差し戻したメッセージがまた自分のところにやってくる可能性もある。

メッセージングシステム側で、メッセージの種類ごとに個別のチャネルを用意するという手もあるが、このソリューションはあまり動的ではない。

というわけで……


Solution

コンシューマーをSelective Consumerにして、チャネルから配送されるメッセージをフィルタリングし、条件にマッチするものだけを受け取るようにする。


フィルタリングプロセスは、上の図の三つのパーツで構成される。

  • Specifying Producer - メッセージの selection value を指定し、メッセージを送信する。
  • Selection Value - メッセージに一つあるいは複数の値を設定し、コンシューマーがメッセージを選択できるようにする。
  • Selective Consumer - 選択条件を満たすメッセージだけを受信する。

シーケンス図は、次のとおり。

送信者は、メッセージにSelection Valueを設定して送信する。Selective Consumerは、その値を見てメッセージを選択する。

いくつかのSelective Consumerを組み合わせて使うことが多い。たとえばローンを扱うシステムなら、「10万ドル以下のローン用」「それをこえる額のローン用」などそれぞれのConsumerを用意する。

複数のSelective ConsumerをPoint-to-Point Channelと組み合わせて使うと、それは事実上、選択可能なCompeting Consumersになる。Selectする条件がダブっているやつがあれば、どっちか先に受け取ったほうが処理する。この構成の場合は、どんなメッセージがやってきてもすくなくともどれかひとつのConsumerが対応できるようにしておかないといけない。じゃないと、誰にも相手をしてもらえずに消えていくメッセージが出てしまう。

複数のSelective ConsumerをPublish-Subscribe Channelと組み合わせて使う場合は、すべてのメッセージがすべてのSubscriberに送られ、条件を満たさないメッセージなら単純に無視するという流れになる。メッセージングシステム側でこのあたりを最適化する余地があって、条件を満たさないことがわかっているメッセージなら、そもそもそのSubscriberには送らないようにするという手もまる。

Selective Consumersを使えば、まるで複数のDatatype Channelsがあるような動きを1つのチャネルでエミュレートできる。データタイプごとに別々のSelection Valueを設定すればいい。これで、データタイプの数だけチャネルを用意する必要はなくなる。

しかしこの手法は、特定のタイプのメッセージは特定のアプリケーションには見えないようにしたいといったときには不適切だ。メッセージングシステムの認証機能は、Consumerの抽出条件まで認証の対象にすることはできない。なので、認証済みのアプリケーションが不正な抽出条件を設定し、本来見てはいけないメッセージをSelectしてしまうということがあり得る。そんな場合は、データタイプごとに個別のチャネルを用意すべき。


ここからは、他のパターンとの比較。

Selective Consumersの代替案として、Message Dispatcherが使える。抽出条件をディスパッチャーに組み込んで、メッセージごとのperformerを決めさせればいい。この2つの違いは、ディスパッチをメッセージングシステムに任せるのか自前で実装するのかというところだけ。

Selective Consumersの代替案として、Message Filtersも使える。どちらもやってることは同じだけど、やりかたが違う。Selective Consumersは、とりあえず全Consumerにメッセージを流す。メッセージを処理するかどうかを決めるのはConsumer。一方Message Filtersの場合、送信者と受信者のチャネルの中に割り込んで、条件を満たすメッセージだけを受信者側に流す。受信者側は単純に、やってきたメッセージを全部処理するだけ。無駄なメッセージを送らずに済むのがMessage Filters。どんなメッセージを受け取りたいのかが受信者によって異なる場合に便利なのが、Selective Consumers。

さらに、Content-Based Routerも検討の対象となる。これを使えば、Filterを使う場合と同様、セキュリティの向上やConsumerのパフォーマンスの向上につながる。しかし、Selective Consumersのほうがより柔軟に使える。Selective Consumersで条件を追加するには、単にConsumerを追加するだけで済む(システムを止めずに追加できる)が、Content-Based Routerの場合は新たな出力チャネルが必要になる。Content-Based Routerは静的なアプローチ、Selective Consumerは動的なアプローチと言える。

Examples

Separating Types

株式取引のシステムで、限られたチャネルの中でquoteとtradeをどちらも扱わなければいけなくなったとする。quoteとtradeではやることが全く違う。こんなときにSelective Consumersを使い、selector valueとしてたとえばQUOTEとTRADEなどを設定すればいい。

JMS Message Selector

JMSでの実装例。

送信側:

   Session session = // セッションを取得する
   TextMessage message = session.createTextMessage();
   message.setText("<quote>SUNW</quote>");
   message.setStringProperty("req_type", "quote");
   Destination destination = // 相手を取得する
   MessageProducer producer = session.createProducer(destination);
   producer.send(message);

受信側:

   Session session = // セッションを取得する
   Destination destination = // 相手を取得する
   String selector = "req_type = 'quote'";
   MessageConsumer consumer = session.createConsumer(destination, selector);

.NET Peek, ReceiveById?, and ReceiveByCorrelationId?

.NETのMessageQueue.Receiveは、JMSみたいなメッセージセレクターをサポートしていない。レシーバー側でできることといえば、MessageQueue.Peekでメッセージを見ることだ。メッセージを見て、取得すべきメッセージだとわかったら、MessageQueue.Receiveでキューから読み込む。


担当者のつぶやき

みんなの突っ込み