この章では、ルーティングや変換のパターンを組み合わせて、より大きなソリューションにまとめる方法を示す。
サンプルのシナリオとして選んだのは、複数の銀行からローンの見積もりをとるという手続きのモデリング。 インテグレーションパターンの議論に集中するため、業務プロセスは少し単純化した。
ローンを組もうとしたとき、普通は複数の銀行に見積もりを依頼する。そして、金利が一番有利なところと契約することになる。
見積もり依頼を受けた銀行は、信用調査機関に問い合わせて顧客の信用情報を調査する。 希望する返済期間やこれまでのクレジット記録に基づいて、金利を提示する(あるいは、取引を丁重にお断りする)。 すべての銀行からの見積もりが出そろったら、一番安い金利を出してきたところと契約する。
この流れを示したのが、次の図。
こんな風に複数の銀行に見積もりを依頼するのは面倒なので、そういった作業を肩代わりするのがローンブローカーだ。
顧客はブローカーに見積もり依頼を出す。それを受け取ったブローカーが信用情報を調べて、適切な銀行に見積もり依頼を出す。 銀行からのオファーを受け取ったブローカーは、一番よいものだけを顧客に返す。
この流れを示したのが、次の図。
この処理を設計していこう。
ローンブローカーに必要なタスクは、次のとおり。
まずは、ブローカーがどうやってメッセージを受け取るのかというところだが、これについては次の第10章で詳しく扱う。 なので、ここではとりあえず、何らかの方法でメッセージがブローカーに到達するものだとして話を進める。
次に、ブローカーは信用情報を取得しなければいけない。ここではContent Enricherが使える。 連絡先の銀行を決めるタスクも、別のContent Enricherを用意すればいい。
複数の銀行にメッセージを投げて、そのレスポンスを単一のメッセージとして受け取る。 これはまさにScatter-Gatherが適任だ。
Scatter-Gatherがリクエストを送信するときには Publish-Subscribe ChannelもしくはRecipient Listが使える。 戻ってきたメッセージをとりまとめるのに使うのはAggregatorだ。
これらを踏まえると、次の図のような設計になるだろう。
ただ、まだこの時点では、銀行によってリクエストやレスポンスのメッセージフォーマットが異なるということを想定できていない。 ルーティングや集約のロジックは銀行ごとの独自フォーマットから切り離しておきたいので、間に Message Translatorを挟むことになる。 個々のレスポンスを共通フォーマットに変換するには、Normalizerが使える。
これを反映させたのが、次の図。
メッセージの処理順(Sequencing)については、同期・非同期の二種類の選択肢がある。
同期型のシーケンス図は、次のようになる。
このソリューションの利点は、シンプルに管理できるということ。並列処理やスレッドは気にせずに済む。 しかし、効率はよろしくない。銀行ごとに別の計算機資源を使っているであろうから、本来はすべてのリクエストを同時に処理できるはず。 なのに、ブローカー側の都合で処理を待たされるので、ユーザーが結果を得るまでにかかる時間は長くなる。
一方、非同期型のシーケンス図は、次のようになる。
このソリューションの利点は、結果を返すまでの時間を短縮できること。 n件の銀行に見積もり依頼を投げたとして、それぞれの銀行での処理時間が同じだと仮定すると、 非同期版で結果を得るまでにかかる時間は同期版のn分の1になる。 ただし、ローンブローカー側での管理が面倒になる。リクエストした順番どおりに結果が返ってくるという保証はないので、 どんな順で答えが返ってきても対応できるようにしておく必要がある。
非同期方式の大きな利点が、もうひとつある。それは、サービスプロバイダーのインスタンスを複数作れるということ。 たとえば信用調査機関のコンポーネントがボトルネックになったとしても、そのコンポーネントを複数走らせればそれで対応できる。 ブローカーはメッセージをキューに送るだけなので、その後実際にメッセージを処理するコンポーネントが何であってもかまわない。
リクエストをどの銀行に配送するのかを指定する方法は、Recipient ListかPublish-Subscribe Channelのいずれかを選べる。判断基準は、まず第一に、ローンブローカーが銀行に対してどのくらいの影響力を行使したいのかだ。選択肢は次の三つ。
どれが正解というわけでもない。さまざまな優先順位や制約に基づいて、どれを採用するかを決めることになる。
「固定」方式は、一番シンプルに管理できる。しかし、銀行の追加や削除がおこるたびに、ブローカー側のリストの更新が必要になる。 また、銀行側にしても、まったく興味がないリクエストを大量に受け取ることになるので、あまりうれしくないだろう。
「分散」方式は、ローンのリクエストごとに、どの銀行に参加させるのかをブローカーが細やかに制御できるようになる。 つまり、リクエストを送る数を効率的に減らせる。さらに、提携関係にある特定の銀行だけを優遇するなどの細工もできる。 ただしこの方式にも弱点がある。ローンブローカーの中に業務ロジックを作り込まなければいけなくなるということだ。
「入札」方式は、リクエストに応えるかどうかを銀行側に決めさせる方式。ローンブローカー側は、ほぼメンテナンス不要になる。 その一方で、銀行側に求められる作業が増える。 Message Filterを使うなりSelective Consumer を実装するなりして自分の欲しいメッセージだけを受け取るのは、銀行側の仕事になる。 ブローカー側のチャネルは、Publish-Subscribe Channelにしなければいけない。 あるいは、別の実装として、Point-to-Point Channelの配列と Recipient Listの組み合わせでPublish-Subscribe Channelをエミュレートする方法もある。
銀行側からの回答を受け取る方法にも選択肢がある。すべての銀行に、単一の同じレスポンスチャネルを使わせることもできるし、 銀行ごとに個別のレスポンスチャネルを用意することもできる。
単一チャネル方式は、銀行ごとに新たなチャネルを用意する手間を軽減できる。しかし、銀行からのメッセージの中に、その見積もりの発行元を表すフィールドを追加する必要がある。 また、単一チャネル方式の場合、Aggregatorがその入札へのレスポンスの数を知るには、 Recipient Listから別途その情報を受け取らなければいけない(この方式を「初期化済みAggregator」と呼んでいた)。
「入札」方式を使った場合は、レスポンスの数を事前に想定できない。そこでAggregatorは、メッセージの数に依存しない完了条件を設定することになる。
ローンブローカーが同時に複数のリクエストを扱えるようにするには、次の二つの方法がある。
最初の選択肢は、たとえばローンブローカープロセスのプールを用意して、リクエストが来るたびにMessage Dispatcherで空きプロセスを割り当てるなどの手がある。空きがなければキューに入れておけばいい。
ただ、ローンブローカーの処理の大半は外部からの返事を待つことなので、多数のプロセスを並列に動かすのは、リソースの使い方としてあまりよろしくない。 個別のメッセージの処理はシンプルなタスクなので、単一のインスタンスでも十分さばけるだろう。リソースの節約にもなるし、システムもシンプルになる。単一インスタンス方式の弱点は、スケーラビリティに欠けること。なので、大規模なアプリケーションの多くは、この二つの手法を組み合わせて使っている。つまり、複数のプロセスを並行稼働させつつ、それぞれのプロセスが複数のリクエストを同時に処理できるようにするということだ。
いろんな実装方針のトレードオフを見るために、代表的な三通りの実装を用意してみた。
実装 | 処理順 | アドレス指定 | 集約方式 | チャネルタイプ | 製品・技術 |
---|---|---|---|---|---|
A | 同期 | 分散 | チャネル(銀行ごとに個別のチャネルを用意する) | Web Service/SOAP | Java/Apache Axis |
B | 非同期 | 分散 | 相関ID(すべての銀行が同じチャネルにレスポンスを返す) | Message Queue | C#/Microsoft MSMQ |
C | 非同期 | 入札 | 相関ID(すべての銀行が同じチャネルにレスポンスを返す) | Publish-Subscribe | TIBCO Active-Enterprise |