EIP / Publish-Subscribe Channel


Publish-Subscribe Channel(パブリッシュ=サブスクライブ・チャネル)

一言要約

Observerパターンのメッセージング版。

Context

Messagingを使っているアプリケーションで、イベントを通知したい。

Problem

そのイベントにかかわるすべての受信者にイベントをブロードキャストするには?

Force

ブロードキャスト機能を実装するパターンとしては、GoFのObserverパターンが有名。POSAのPublisher-Subscriberパターンはそれを拡張したもので、イベントチャネルという概念が追加された。

理論上はそういうことだが、それをメッセージングの世界に適用するとどうなるだろうか。イベントはMessageとしてとりまとめられているので、Observer(Subscriber)に確実に渡せる。イベントチャネルにあたるのはMessage Channelだ。でも、Message ChannelがSubscriberへ確実にイベントを配送するにはどうすればいい?

Subscriberはイベントの通知を受け取る必要があるが、同じイベントの通知を何度も受け取るようではいけない。すべてのSubscriberが通知を受け取って、はじめてそのイベントが完了したことになる。完了したイベントは、チャネルから消さないといけない。

Subscriberどうしで示し合わせて同じタイミングでイベントを受け取るなんてことをすると、せっかくObserverパターンで結合を切り離したのが台無し。


Solution

イベントをPublish-Subscribe Channelに送る。そしてPublish-Subscribe Channelがそのコピーを各受信者に配送する。

ひとつの入力チャネルがあって、出力は(subscriberごとに)複数のチャネルに分割する。あるイベントがチャネルに配信されると、そのメッセージのコピーを各出力チャネルに配送する。チャネルの出力側はそれぞれひとつづつのsubscriberを持ち、メッセージを一度だけ受け取れるようになっている。それを使って各subscriberが一度だけメッセージを受け取り、受け取り済みのコピーはチャネルから消えるようにする。

あと、このパターンはデバッグツールとしても有用だ。たとえメッセージの受信者がひとつだけであるときでも、敢えてこのパターンを使ってもいい。そうすれば、既存のメッセージの流れを乱さずにメッセージチャネルを傍受できるようになる。チャネル上のすべてのトラフィックを監視できるので、わざわざアプリ側にprint文を埋め込んだりせずに済む。

しかし、Publish-Subscribe Channel上で盗み聞きできるという事実は弱点にもなる。給与システムと会計システムの間で給与データを送信するようなメッセージングソリューションがあったとしよう。ちょっとしたプログラムを書くだけで誰でもそのトラフィックをのぞき見できるというのは好ましくない。一方Point-to-Point Channelなら、その懸念は緩和できる。もし誰かがメッセージを横取りしたら、そこでメッセージが消えてしまうのですぐに問題を検出できるからだ。しかし、メッセージキューの実装の多くはpeek機能を提供しており、メッセージをconsumeしなくてもその中身を読めるようになっている。結局はセキュリティポリシーで縛るしかないということだ。

Event Messageの送信には、通常はこのパターンを使う。ひとつのイベントに関連する受け手が複数あることが多いからだ。もしsubscriber側での受信確認が必要ならRequest-Replyを使う。すべてのsubscriberがメッセージを受け取るまでPublish-Subscribe Channel上の各メッセージを保存しておくには、大量のストレージが必要だ。この問題を軽減するために、Publish-Subscribe Channelにメッセージを送るときにはMessage Expirationが使える。

Wildcard Subscribers

多くのメッセージングシステムは、Publish-Subscribe Channelへのsubscriberを指定するときにワイルドカード文字を使える。ただワイルドカードを使えるのはsubscriber側だけで、publisherは常に配信先のチャネルを指定しなければならない。ベンダーによって、ワイルドカードの構文は異なる。

Examples

Stock Trading

株式取引の話。全購読者に対して取引完了を通知したり、現在の市況データを配信したりするときにこのパターンを使える。

JMS Topic

JMSでこのパターンを実装するにはTopicインターフェイスを実装する。

メッセージを送信するアプリケーションの例。

Topic topic = // JNDIから取得する
TopicConnectionFactory factory = // JNDIから取得する
TopicConnection connection = factory.createTopicConnection();
TopicSession session = connection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
TopicPublisher publisher = session.createPublisher(topic);

Message message = session.createTextMessage("The contents of the message.");

publisher.publish(message);

メッセージを受信するアプリケーションの例。

Topic topic = // JNDIから取得する
TopicConnectionFactory factory = // JNDIから取得する
TopicConnection connection = factory.createTopicConnection();
TopicSession session = connection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
TopicSubscriber subscriber = session.createSubscriber(topic);

TextMessage message = (TextMessage) subscriber.receive();
String contents = message.getText();

MSMQ One-to-Many Messaging

MSMQ 3.0にはone-to-many messaging modelという新機能があり、二種類の手法が使える。

Real-Time Messaging Multicast
Publish-Subscribe方式に最適な手法だが、その実装はPragmatic General Multicastプロトコルを使ったIPマルチキャスティングに依存する。つまり、IPベースのプロトコル上でしか使えない。
Distribution Lists and Multiple-Element Format Names
Distribution Listを使うと、リスト上の受信者に対して送信者から明示的にメッセージを送れる。これはどちらかというと、Publish-Subscribe ChannelよりもRecipient Listに近い。Multiple-Element Format Nameとはチャネルを表すシンボルで、複数の実チャネルに対して動的にマップされる。これはPublish-Subscribe Channelの考え方に近いが、実チャネル名を使うかシンボルを使うかの選択を送信者に強いることになってしまう。

.NETのCLRは一対多のメッセージングモデルに直接は対応していない。しかし、COMインターフェイス経由でのアクセスはできる。

Simple Messaging

JMSを使ったサンプルを第6章に書いたので見てね。

担当者のつぶやき

もう少しちゃんと要約したかったけど、時間の都合でほぼ全訳垂れ流しみたいになってしまいました。

みんなの突っ込み