EIP / Asynchronous Implementation with MSMQ


Asynchronous Implementation with MSMQ

ひとこと要約

ローンブローカーのMSMQによる実装サンプル

要約

MSMQでローンブローカーを実装する。詳細はイントロダクションで。

Microsoft BizTalk? Serverを使うことでコーディングする量を減らせるが、次の2つの理由から使ってない。

  • 有償である
  • 必要な機能の実装を明らかにしたかった。

異なるコンポーネントは複数のコンピュータで分散して稼働できるようにした。また、Active Directoryのインストールを避けてシンプルにした。

ローンブローカーの実装は、メッセージキューを通して非同期メッセージングを使うサンプルとなってる。非同期処理の必要性によって多くの設計判断がなされたサンプルとなってる。

Loan Broker Ecosystem

外部からローンブローカーの設計を解説。P.402の図で外部のインタフェースを確認すべし。メッセージキューが単方向性なので、別コンポーネントとリクエスト/レスポンス通信を確立するためのキューのペアが必要となる。ローンブローカーはloanRequestQueue?でローン見積のリクエストを受け取り、loanReplyQueue?でリプライを送る。信用機関とのやりとりも同様。Recipient Listがそれぞれの銀行のキューにリク絵エストメッセージを投げ、AggregatorがloanReplyQueue?(※bankReplyQueue?の間違いでは?)に届いたリプライの中から一番良い見積りを選択する。Recipient ListAggregatorScatter-Gatherパターンである。簡単にするために、サンプルではNormalizerを使わないよう統一フォーマットを使用してるが、銀行からのリプライメッセージはローンブローカーのものとは異なるフォーマットにしているので、Message Translatorで変換している。また、ローンブローカーはProcess Managerを適用している。メッセージキューごとに別々のコンポーネントとは設計せずに、単一のコンポーネントの中で全て実装した。

Laying the Groundwork: A Message Gateway

MSMQに特化した機能をクラスに分離することで、コードが散らからない。Gateway(EAA)はこれを満たすのに2つの利点がある。

  • アプリケーションの通信の技術的な詳細を抽象化する
  • ゲートウェイの実装からゲートウェイのインターフェイスを分離するときに、テスト用に外部のサービスをService Stub(EAA)に置き換えできる

IMessageSender?とIMessageReceiver?の二つのインタフェースをMessaging Gatewayとして定義する。レシーバはメッセージを受信可能であることを知らせるBeginメソッドを持つ(P.403の図とソースコード参照)。

それぞれMessageSenderGateway?MessageReceiverGateway?が実装クラスとなる。これらはMessageReadPropertyFilter?やFormatterの設定といった、キューのプロパティの設定を扱う。MessageReceiverGateway?はTemplate Method(GoF)を使ってMSMQのReceiveCompleted?イベントを扱う。

狭いインターフェイスを定義したので、メッセージキューを使わない実装も可能となる。MockQueue?はメッセージキューを参照することなく両方のインターフェイスを実装する。アプリケーションがメッセージを送ると、MockQueue?は直ちにOnMessage?イベントを発火する。これにより、非同期の側面を気にすることなくアプリケーションをテストすることができる。


Base Classes for Common Functionality

高所から見ると、いくつかのコンポーネントは共通の機能を持つ。例えば、銀行と信用機関はリクエストを受け取って、処理して、その結果を別のチャネルに配信する。簡単に聞こえるが、現実的にはシンプルなリクエストリプライのやりとりでも、ちょっとしたおまけの作業が発生する。まずは、リプライのReturn Addressの特定。そしてリクエストの送信者がどのリクエストに対するリプライなのかを特定できるよう、Correlation Identifierもサポートすべき。さらに、不明なフォーマットのメッセージが届いた場合は、Invalid Message Channelを使うのも良い。

サンプル(実装例はP.406)ではコードの重複を避けるのに、MQServiceクラスを作成。Return AddressCorrelation Identifierをサポートしている。実際にはサーバ側でCorrelation IdentifierをサポートするにはメッセージIDをコリレーションIDにコピーする程度のものだが、このサンプルではAppSpecific?プロパティもコピーしている。というのは、後に突き合わせる必要が出たときに、単なるメッセージIDではなくプロパティを使うこともあるので。それから、MQServiceはレスポンスを必ず特定のReturn Addressに送出する。リクエストの送信者がReturn Addressを供給するので、MQServiceには初期パラメータとしてリクエストキューの名称を与えるだけ。リクエストの送信者がReturn Addressの供給をしなかった場合は、RequestReplyService?Invalid Message Channelにリプライを送信する。

GetRequestBodyType?OnMessage?メソッドを実装してないのでabstract。メッセージのデータタイプよりは強く型付けされたビジネスオブジェクトとして扱いたいので、MQServiceはメッセージボディの妥当性を評価し、適切な型にキャストする。が、この基本クラスはどのクラスにキャストすべきなのかを知らない。基本クラスでより機能するよう、GetTypedMessageBody?とabstractなGetRequestBodyType?を作成する。MQServerはXMLのフォーマッターの初期化と型チェックを行う。サンプルでの例外処理は単にコンソールにメッセージを出してるだけだが、シンプルなものとしないのであれば、Control Busを使うのもあり。

OnMessage?メソッドは2種類の実装、同期的なもの(RequestReplyService?)と非同期的なもの(AsyncRequestReplyService?)を用意した。RequestReplyService?はリプライメッセージを返すProcessMessage?を呼び出し、続いてSendReply?を呼び出す。対照的にAsyncRequestReplyService?は、ProcessMessage?は何のパラメータも返さない。継承するサブクラスはSendReply?を呼び出す必要がある。

どちらのクラスもGetRequestBodyType?ProcessMessage?のデフォルトの実装を提供している。GetRequestBodyType?はメッセージがStringの型かどうかを特定し、ProcessMessage?はコンソールに文字列を出力するだけ。こういうことはやめてabstractのままにしておくというのもあるが、テストとデバッグを目的とするのには有用。

まとめとして、クラス図はP.409に。

Designing the Bank

土台は揃ったのでそろそろアプリケーションロジックへ。依存関係の逆順に実装するのが簡単。というのは、最初に作るのはどれにも依存しないから。これでテストも簡単。銀行はコンポーネントの一つで、ローンブロオーカーは銀行に依存する。ということで、まずは銀行から。RequestReplyService?を継承し、いくつかのビジネスロジックを実装する。

内部の処理を進める前に、外部のインターフェイスを定義しておく。まずはローンの見積りリクエストとリプライのメッセージの型。単純にするのに、全ての銀行は共通のメッセージフォーマットを使用しているとする。ソースコードはP.410。

すべての銀行のインスタンスを単一のクラスで表現したいので、異なる振る舞いをするのにBankName?RatePremium?MaxLoanTerm?といったパラメータが必要となる。RatePremium?は保険料率、基本的には銀行の利鞘、をもとに決められる利息率を決める。MaxLoanTerm?はローンの最長期間。その期間を超えたリクエストは丁重に断るとした。ソースコードはP.411。

ほとんどの実装は親クラスでなされている。MQServiceとRequestReplyService?Service Activatorとして振る舞い、アプリケーションをメッセージングシステムの詳細から離している。

ComputeBankReply?メソッドが、銀行のビジネスロジックを含んでいる。プライムレート、保険料率、ローンの期間、乱数の合計から利息を算出。期間が銀行の設定値より長ければエラーコードを設定して返す。それぞれの見積りには見積りIDが割り振られる。

ProcessMessage?メソッドでは、銀行の処理をリアルにするのにちょっとディレイをかけたり、ログを撮ったりしてる。

銀行を開始するのには、適切なパラメータを与え、Runメソッドを呼び出す。イベントとして処理は呼びだされるので、プログラムが終了しないように、Runメソッドの呼び出し後にConsole.ReadLine?()を呼び出す。

Designing the Credit Bureau

信用機関の実装は銀行と似たものとなる。違いというとメッセージの型とビジネスロジックだけ。P.412にある型を扱う。

ProcessMessage?メソッドは銀行のとほとんど似たもので、違いはデータの構造と異なるアプリケーションロジックを呼び出すことくらい。

Designing the Loan Broker

今私たちは、信用調査を行い、銀行の複数を具体的にインスタンス化する銀行クラスを持ち、ローンブローカーの内部設計を行う準備が整いました。この本からのルーティングおよび変換パターンは、ローンブローカーが提供する必要のある機能を分割することを助ける。ローンブローカーの内部機能は3つの部分にグループ化することができる。(図を参照)

​クライアントからの要求を受け入れる「要求 - 応答インタフェース」、「信用調査インタフェース」、そして「銀行インターフェース」である。

(図:ローンブローカーの内部構造)

依存関係の逆の順序で全体のソリューションを構築するのと同じような方式で、唯一そこに既にあるものに依存する部分の構築を始めましょう。単に銀行や信用調査サービスを構築しているので、それはローンブローカーからこれらの外部コンポーネントへのインターフェイスを作成することは理にかなっています。信用調査インターフェイスは間違いなくシンプルなので、そこから始めましょう。

Credit Bureau Gateway(信用調査ゲートウェイ)

ローンブローカーは、銀行によって必要とされる顧客の信用格付けを取得するために信用調査機関に要求を行う必要がある。これは、外部のローンブローカー·コンポーネントにメッセージを送信し、応答メッセージを受信することを意味する。MessageGateway?内部の一般的なメッセージ送信の詳細をラッピングすると、アプリケーションの他の部分から多くのMSMQの詳細を隠すことができる。さらに同じ理由で、信用調査ゲートウェイ内部の信用調査へのメッセージ送信および受信をカプセル化するべきだ。この信用調査ゲートウェイは、SendMessage?とは対照的に、ローンブローカーにGetCreditScore?などのメソッドを呼び出すことができるように、意味的な拡張を実施する重要な機能を果たします。これは、ローンブローカーのコードが読みやすくなり、ローンブローカーと信用調査機関の間の通信の強力なカプセル化を提供します。次の図は、 2つのゲートウェイを"連鎖"することによって達成される、抽象化のレベルを示しています。

(図:ローンブローカーは、メッセージングインフラストラクチャから、追加の抽象化レベルを提供します。)

クレジットスコアを要求するために、ゲートウェイは、信用調査インタフェースによって指定さCreditBureauRequest?構造体のインスタンスを作成する必要があります。同様にして、インターフェースはCreditBureauReply?構造体の内部にある結果を受け取ります。始めに、ソリューションは、信用調査インタフェースがローン·ブローカーとは異なるコンピュータ上で実行できるように別の実行体から構築されていることを述べた。これは、ローンブローカーは信用調査インタフェースのアセンブリで定義された型へのアクセスを持っていない可能性があることを意味します。そして、メッセージ·キューを介しての疎結合の利点を失ってしまうため、ローンブローカーは信用調査インタフェースの内部への参照をつくってしまうのは望ましくない。ローンブローカーはクレジットスコアを要求するサービスを完全に知らないことになっています。ローンブローカーは、メッセージ形式を定義する構造体を必要としますが。幸いにも、マイクロソフトのNET FrameworkのSKDには、それを行うことができるXMLスキーマ定義ツール(Xsd.exe)が含まれています。このツールは、アセンブリからXMLスキーマを作成し、またXMLスキーマからC#のソースコードを作成することができます。次の図は、このプロセスについて説明します。

(図:別のアセンブリからクラススタブの作成する)

Xsd.exeは型定義を抽出し、制御シリアライゼーションその型定義と追加属性(attribute)に基づいて、XMLスキーマファイルを作成します。例では、Xsd.exeは次のスキーマを作成しました:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="CreditBureauRequest" type="CreditBureauRequest" />
  <xs:complexType name="CreditBureauRequest">
   <xs:sequence>
     <xs:element minOccurs="1" maxOccurs="1" name="SSN" type="xs:int" />
   </xs:sequence>
  </xs:complexType>
  <xs:element name="CreditBureauReply" type="CreditBureauReply" />
  <xs:complexType name="CreditBureauReply">
   <xs:sequence>
     <xs:element minOccurs="1" maxOccurs="1" name="SSN" type="xs:int" />
     <xs:element minOccurs="1" maxOccurs="1" name="CreditScore" type="xs:int" />
     <xs:element minOccurs="1" maxOccurs="1" name="HistoryLength" type="xs:int" />
   </xs:sequence>
  </xs:complexType>
 <xs:element name="Run" nillable="true" type="Run" />
 <xs:complexType name="Run" />
</xs:schema>

通常、サービスは、潜在的な呼び出し元に対して、このスキーマ定義を公開します。これは、オプションの異なるいくつかの方法で、発信者が必要なメッセージフォーマットを生成することができます。まず、XSDに準拠で構築することができます。また、NET組み込みのシリアル化を使用することもできます。NETのCLRはプログラミング言語に依存しないので、プログラミング言語の選択肢を持っている。

ここでは、.NET組み込みのシリアル化を使用することにした。したがって、サービスコンシューマが使用するソースファイルを作成するためにもう一度xsd.exeを実行します。そして、このようなファイルを手に入れます:

//
// This source code was auto-generated by xsd, Version=1.1.4322.573.
//
namespace CreditBureau {
   using System.Xml.Serialization;
   /// <remarks/>
   [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
   public class CreditBureauRequest {
       /// <remarks/>
       public int SSN;
   }
...

.NET XMLシリアル化と逆シリアル化が疎結合を可能にすることは注目に値する。そして厳密に言えば、信用調査に送信する要求メッセージには、必要な要素がXMLに含まれている限り、信用調査の実装の内部で使用されるタイプと正確に同じである必要はありません。例えばこれは、XMLに追加要素を含んでいても、問題なくメッセージ送信できる。

この例では、ローンブローカーは信用調査の指定された形式に準拠し、通信の両端で同じタイプを使用する意思があることを想定しています。

今、信用調査に正しい形式でメッセージを送信する準備が整いました。私たちは、これが非同期要求メッセージと応答メッセージが分離された非同期通信であることを心に留めておく必要があります。我々は、ゲートウェイコードが、要求を送信した後に、戻ってくる応答を待つように、信用調査ゲートウェイを設計することができます。このアプローチは、1つの重要な欠点があります:アプリケーションは、信用調査がメッセージを処理している間、ただ座って待機します。この擬似同期タイプの処理は、パフォーマンス上のボトルネックになる可能性があります。もしも擬似同期プロセスの各ステップを加えた場合、ローンブローカーは一度に一つの要求プロセスだけを処理できることを意味します。例えば、まだ以前の見積もり依頼のために銀行の応答を待っている間、新たなクレジットスコアを要求することができないだろう。

違いを視覚化するために、ローンブローカーの2つのステップである:「クレジットスコアを取得する」「そして銀行からの最高の見積もりを取得する」を考えてみよう。

もしもローンブローカーが、単一順次実行すると仮定した場合、実行は次の図の上半分のようになります。

(図:パイプライン処理は有意に高いスループットを提供することができます)

実際の作業の大部分は、外部コンポーネントによって実行されるため、ローンブローカー·コンポーネントは、基本的には周りに座って結果を待​​つ -- コンピューティング·リソースの効率的に使用しない。イベント·ドリブンコンシューマとして全体のローンブローカープロセスを設計した場合、並行して複数の要求の処理を開始することができ、戻ってきた結果を処理する私たちは、このモード を'パイプライン'処理と呼び出します。システムのスケーラビリティは、ローンブローカーではなく、外部コンポーネントの処理能力にだけ依存します。もしも信用調査プロセスを単一インスタンスで実行する場合は、その差はハッキリとしません。しかし、並列に複数の信用調査インスタンスを実行することにした場合、ただちにパフォーマンスが向上します。

イベントドリブンのローンブローカープロセスを作るには2つの主要な方法があります。一度だけ一連のプロセスを作成し、各受信メッセージ用に新しいスレッドを作成することができます。あるいは、イベントがペンディングされるたびに、メッセージングシステムはローンブローカーに通知することができます。この方法は、メッセージングシステムが実行スレッドを管理します。それぞれのアプローチは、長所と短所を持っています。一連のプロセスのコーディングは、理解するのが簡単なコードとなります。しかし、もしコンポーネントが、主に外部のエンティティ間のメッセージを交換するブローカーコンポーネントなら、着信メッセージを待つ潜在的に多数のスレッドとなります。

 

これらのスレッドは、システムリソースを大量に消費し、少ししか処理を成し遂げられない。したがって、メッセージングシステムが実行をドライブするようにしたほうが良いかもしれません。メッセージが消費される準備ができているときはいつでも、システムは、ブローカーの実行を起動します。これは、単一の実行スレッドを維持し、スレッド管理については心配する必要がないようにします。しかし、実行パスが単一のシーケンシャル方式でなく、メッセージが到着したときに複数のコード·セグメントが実行されるという事実に対処する必要がある。

あなたは.NETでイベントドリブンにする方法は、デレゲートと推測しているかもしれません。予想通り、信用調査ゲートウェイは新しいデリゲートを定義しています。

public delegate void OnCreditReplyEvent(CreditBureauReply creditReply, Object ACT);

このデリゲートは、結果が入って来たときに呼び出すべきコード部分を信用調査ゲートウェイに伝えることができます。信用調査ゲートウェイは、呼び出し元に正しく型付けされたCreditBureauReply?構造体を渡します。それはまた、我々がACTと呼ぶ何かを渡します。-- 非同期完了トークン[POSA] このトークンは、送信者がゲートウェイにデータを渡し、対応する応答メッセージが戻ってくるときに、データの戻りを受け取ることができるようになります。基本的には、信用調査ゲートウェイが、要求と応答メッセージの相関を処理し、送信者は処理する必要がありません。

クレジットスコアと適切なACTとの相関関係を把握し、適切に型付けされたデリゲートを呼び出して、着信メッセージを処理するメソッドを要求する方法です。

CreditBureauGateway?.cs

internal struct CreditRequestProcess
{
   public int CorrelationID;
   public Object ACT;
   public OnCreditReplyEvent callback;
}
internal class CreditBureauGateway
{
   protected IMessageSender creditRequestQueue;
   protected IMessageReceiver creditReplyQueue;
   protected IDictionary activeProcesses = (IDictionary)(new Hashtable());
   protected Random random = new Random();
   public void Listen()
   {
       creditReplyQueue.Begin();
    }

   public void GetCreditScore(CreditBureauRequest quoteRequest, OnCreditReplyEvent
OnCreditResponse, Object ACT)
   {
       Message requestMessage = new Message(quoteRequest);
       requestMessage.ResponseQueue = creditReplyQueue.GetQueue();
       requestMessage.AppSpecific = random.Next();
       CreditRequestProcess processInstance = new CreditRequestProcess();
       processInstance.ACT = ACT;
       processInstance.callback = OnCreditResponse;
       processInstance.CorrelationID = requestMessage.AppSpecific;
       creditRequestQueue.Send(requestMessage);
       activeProcesses.Add(processInstance.CorrelationID, processInstance);
   }
   private void OnCreditResponse(Message msg)
   {
       msg.Formatter = GetFormatter();
      CreditBureauReply replyStruct;
      try
      {
         if (msg.Body is CreditBureauReply)
         {
            replyStruct = (CreditBureauReply)msg.Body;
            int CorrelationID = msg.AppSpecific;
            if (activeProcesses.Contains(CorrelationID))
            {
                CreditRequestProcess processInstance = (CreditRequestProcess)(activeProcesses[CorrelationID]); request"); }
         }
  processInstance.callback(replyStruct, processInstance.ACT);
  activeProcesses.Remove(CorrelationID);
}
else { Console.WriteLine("Incoming credit response does not match any
else
   { Console.WriteLine("Illegal reply."); }
}
       catch (Exception e)
{
          Console.WriteLine("Exception: {0}", e.ToString());
       }
} }

呼び出し側がGetCreditScore?メソッド経由でクレジットスコアを要求すると、信用調査ゲートウェイはCreditRequestProcess?構造体の新しいインスタンスを割り当てます。コレクションactiveProcessesは、未処理の要求ごとにメッセージの相関IDをキーとして、CreditRequestProcess?のインスタンスを含んでいます。構造体はOnCreditReplyEvent?イベントのデリゲートについても保持しています。メッセージごとにデリゲートを格納すると、それぞれの呼び出し側は、要求ごとに異なる​​コールバックのコードを指定することができます。後で見るように、これは呼び出し元が会話の状態を管理するためにデリゲートを使用することができます。

相関のための、メッセージの組み込みIDフィールドを使用していないことに注意することが重要です。その代わりに、AppSpecific?フィールドにランダムな整数を代入し、そのフィールドの値によって、着信メッセージを相関させます。(応答メッセージにIdフィールドとAppSpecific?フィールドの両方をコピーするRequestReplyService?を設計していることを思い出してください)​​なぜメッセージIDではなく、その他のものによって相関するのでしょうか?メッセージIDの利点は、システム内の各メッセージに対して一意であることである。しかし、それはまた、私たちの柔軟性を制限します。応答メッセージに、要求メッセージのメッセージIDを相関のために要求すると、メッセージ·フローに中間ステップ(例えば、ルータ)を挿入することができません。なぜなら、どのような仲介ステップであっても、要求メッセージを消費し、サービスへの新しいメッセージを発行することになるので、応答メッセージの相関IDは、サービスが受信したメッセージではなく、ローンブローカーがもともと送​​信したメッセージと一致します。(写真参照)

(図:仲介者が、システムが生成したメッセージIDによる相関を妨げる図)

この問題には2つの解決策があります。1つめは、どのような仲介も、要求と応答メッセージの両方をインターセプトし、正しい相関ID値によって応答メッセージをセットアップすることを確信できる必要がある。(このアプローチの例については、スマートプロキシを参照)あるいは代わりに、相関の目的で別のフィールドを使用して、すべての関連するメッセージの仲介とサービスのフローを通して、同じIDを運ぶようにすることができます。今回は、ローンブローカーやクレジットビューローとの間の要求メッセージを傍受する中間コンポーネントを容易にするために、第二のアプローチを選択しました。(ローンブローカーシステム管理でこれを活用します)

     

どのようなAppSpecific?プロパティの値を取得する必要があるのでしょうか?私たちは、連続した値を使用することができます、しかし、2つ​​の同時インスタンスが同じ開始値を使用することのないように注意しなければならない。また、システム全体の一意性を保証する中央のID生成モジュール(データベースなど)を使用することもできます。これは、この単純な例のために少しあまりにも面倒に思えます。それで乱数を選びました。. NETは、重複の確率は10億2になる、32ビット符号付き整数として乱数を生成します。 - とることをいとわないリスクだ。

信用調査ゲートウェイは今、目指していたWindowsのメッセージ·キューイング·インフラストラクチャからのクリーンな抽象化を提供します。信用調査ゲートウェイ(ただしコンストラクタ以外)への唯一のパブリックインターフェイスは、デリゲートと2つのメソッドです:

delegate void OnCreditReplyEvent(CreditBureauReply creditReply, Object ACT);
void Listen() {...}
void GetCreditScore(CreditBureauRequest quoteRequest,OnCreditReplyEvent
OnCreditResponse, Object ACT) {...}

メッセージもメッセージ·キューも参照を構築しない。これは、多くの利点を提供します。まず、簡単にすべてのメッセージキューに依存しない(MockQueue?に似ています)信用調査ゲートウェイのスタブバージョンを実装することができます。第2に、MSMQ異なるトランスポートを使用することを決定した場合、信用調査ゲートウェイの実装を置き換えることができます。例えば、MSMQの代わりにSOAPとhttpのWebサービスを使用する場合、ゲートウェイによって公開されるメソッドを全く変更する必要はありません。

Bank Gateway(銀行ゲートウェイ)

銀行ゲートウェイの設計は、信用調査ゲートウェイの設計と同じ設計原理に従っています。前と同じプロセスを使用して、銀行で指定された要求と応答メッセージの種類のスタブを宣言する。銀行ゲートウェイの外側部分は、信用調査ゲートウェイに非常に似ています:

delegate void OnBestQuoteEvent(BankQuoteReply bestQuote, Object ACT);
class BankGateway {
   void Listen() {...}
void GetBestQuote(BankQuoteRequest quoteRequest, OnBestQuoteEvent onBestQuoteEvent, Object ACT) {...}

... }

内部の仕組みはもう少し複雑です。なぜなら、相互作用のスキャッタ·ギャザースタイルは、複数の銀行に単一のBankQuoteRequest?をルーティングしているためです。同様に、単一のBankQuoteReply?は、通常、複数の銀行の引き合い返信メッセージの結果である。前者の部分はRecipient List(受信者リスト)によって処理され、後者の部分がAggregator(アグリゲータ)によって処理されます。受信者リストから始めましょう。

受信者リストには、3つの主要な機能を実装しなければなりません:

・適切な受信者の算出 ・受信者へのメッセージの送信 ・着信した応答を処理するためのアグリゲータを初期化

設計概要(Composed Messaging Examplesのイントロダクション)で説明したように、この実装では、積極的に銀行へのリクエストのルートを決定するスキャッタ·ギャザーの分散スタイルを使用しています。銀行がそれぞれの引き合いのために請求したり、事前資格審査を導くためのブローカーの必要とする契約を締結している場合、このアプローチは、ビジネスの理にかなっている。ローンブローカーは、顧客のクレジットスコア、ローンの額および信用履歴の長さにもとづいてルーティング決定を行います。抽象BankConnection?クラスから継承した内部の銀行クラスに、各接続をカプセル化します。このクラスは、適切に対処されるメッセージキューと、引き合い依頼がこの銀行に転送されるべきかどうかを決定するメソッドであるCanHandleLoanRequest?への参照が含まれます。BankConnectionManager?は、単にすべてのBankConnection?のリストを反復処理し、ローンの引き合いの​​基準に一致するもののリストを作成します。もしも銀行のリストが長くなった場合、設定可能なルール·エンジンを実装することを検討することもできます。シンプルかつ明確であるため、現在のアプローチを選択する。

   
internal class BankConnectionManager
{
static protected BankConnection[] banks = {new Bank1(), new Bank2(), new Bank3(), new Bank4(), new Bank5() };
 
public IMessageSender[] GetEligibleBankQueues(int CreditScore, int HistoryLength, int LoanAmount)

   {
       ArrayList lenders = new ArrayList();
       for (int index = 0; index < banks.Length; index++)
       {
          if (banks[index].CanHandleLoanRequest(CreditScore, HistoryLength,LoanAmount))
            lenders.Add(banks[index].Queue);
       }
      IMessageSender[] lenderArray = (IMessageSender[])Array.CreateInstance(typeof(IMessageSender), lenders.Count);
      lenders.CopyTo(lenderArray);
      return lenderArray;
   }
}

internal abstract class BankConnection
{
   protected MessageSenderGateway queue;
   protected String bankName = "";
   public MessageSenderGateway Queue
   {
       get { return queue; }
   }
   public String BankName
   {
       get { return bankName; }
   }
   public BankConnection (MessageQueue queue) { this.queue = new MessageSenderGateway(queue); }
   public BankConnection (String queueName) { this.queue = new MessageSenderGateway(queueName); }
   public abstract bool CanHandleLoanRequest(int CreditScore, int HistoryLength, int LoanAmount);

} 

internal class Bank1 : BankConnection
{
   protected String bankname = "Exclusive Country Club Bankers";
   public Bank1 () : base (".\\private$\\bank1Queue") {} 
   public override bool CanHandleLoanRequest(int CreditScore, int HistoryLength, int LoanAmount)
  {
       return LoanAmount >= 75000 && CreditScore >= 600 && HistoryLength >= 8;
} }
...

いちど関連する銀行のリストが作成されると、メッセージを送信して、リストを反復処理するだけです。本番アプリケーションでは、この反復は、メッセージがいくつかの銀行に送られたり、送られなかったりするエラー状態を回避するために、単一のトランザクション内に存在する必要があります。もう一度、この例のために単純に優先させることを選んだ。

internal class MessageRouter
{
public static void SendToRecipientList (Message msg, IMessageSender[] recipientList) {

       IEnumerator e = recipientList.GetEnumerator();
       while (e.MoveNext())
       {
          ((IMessageSender)e.Current).Send(msg);
       }

} }

要求メッセージ後、銀行からの引き合いが戻ってくるので、アグリゲータを初期化する必要があります。ローンブローカーのイベント駆動型の性質に起因して、アグリゲータは同時に複数の集合で動作するように準備する必要があります。 -- 保留されているそれぞれの引き合い要求のための、1つのアクティブなアグリゲートを維持。 これは、受信メッセージを一意に特定のアグリゲートと相関させる必要があることを意味する。残念ながら、受信者リストは銀行のそれぞれに個別のメッセージを送信する必要があるため、相関IDとしてメッセージIDを使用することはできません。結果として、もしも3銀行が引き合い要求に参加した場合、受信者リストには、3つのユニークなメッセージを各銀行にに1つづつ送信する必要があります。これらのメッセージは、それぞれ一意のメッセージIDを持つことになります。もしも銀行がメッセージIDによって相関した場合、3つの応答は、同じアグリゲートに属しているにもかかわらず、異なる相関IDを持つことになります。各メッセージのメッセージIDをアグリゲートは保持しているので、アグリゲートに戻ってくる受信メッセージの相関IDを関連付けることができます。しかし、これは必要以上に複雑に思われる。単に自身の相関IDを生成する代わりに、各メッセージにそれぞれのアグリゲートを1対1に対応づけることは。私たちは、発信する要求メッセージのAppSpecific?プロパティにこの(数値)IDを格納します。銀行は、応答メッセージを受信メッセージのAppSpecific?プロパティをすでに転送するRequestReplyService?から継承する。引き合いメッセージが銀行からくると、BankGateway?は簡単にメッセージのAppSpecific?プロパティで、着信メッセージを相関させることができます。(図を参照)

BankGateway?はアグリゲートに応答メッセージを相関させるAppSpecific?メッセージ·プロパティを使用しています)

銀行ゲートウェイはアグリゲートID(単純なカウンタによって生成された)と予期されるメッセージの数でアグリゲートを初期化します。また、呼び出し側はデリゲートを供給する必要があり、そして必要に応じて信用調査ゲートウェイが機能するのと同様に、ACTにオブジェクト参照を指定することができます。アグリゲーション戦略は単純です。アグリゲートは、選択したすべての銀行が応答メッセージで応答したときに完了したとみなされます。受信者リストは予想されるメッセージでアグリゲートを初期化する。

       

銀行が引き合いの提供をを辞退するオプションを持っていることを忘れないでください。アグリゲートが完了したことが分かるので、引き合いを提供したくない場合は、エラーコードが設定された応答メッセージを提供する銀行を必要とします。我々は簡単にアグリゲーション戦略を変更することができます。例えば、1秒後に入札を切り、その時点までの最高のレスポンスを取るような。

internal class BankQuoteAggregate
{
   protected int ID;
   protected int expectedMessages;
   protected Object ACT;
   protected OnBestQuoteEvent callback;
   protected double bestRate = 0.0;
   protected ArrayList receivedMessages = new ArrayList();
   protected BankQuoteReply bestReply = null;
   public BankQuoteAggregate(int ID, int expectedMessages, OnBestQuoteEvent callback, Object ACT)

  {
       this.ID = ID;
       this.expectedMessages = expectedMessages;
       this.callback = callback;
       this.ACT = ACT;
}

   public void AddMessage(BankQuoteReply reply)
   {
       if (reply.ErrorCode == 0)
       {
          if (bestReply == null)
          {
             bestReply = reply;
          }
else {

             if (reply.InterestRate < bestReply.InterestRate)
             {
                  bestReply = reply;
             }
} }

       receivedMessages.Add(reply);
   }
   public bool IsComplete()
   {
       return receivedMessages.Count == expectedMessages;
   }
   public BankQuoteReply getBestResult()
   {
       return bestReply;
   }
   public void NotifyBestResult()
   {
       if (callback != null)
       {
          callback(bestReply, ACT);
       }
} }

銀行コネクションマネージャ、受信者リストおよび銀行ゲートウェイの実装であるアグリゲートの実装は、比較的シンプルになる。

internal class BankGateway
{
   protected IMessageReceiver bankReplyQueue;
   protected BankConnectionManager connectionManager;
   protected IDictionary aggregateBuffer = (IDictionary)(new Hashtable());
   protected int aggregationCorrelationID;
   public void Listen()
   {
       bankReplyQueue.Begin();
   }
   public void GetBestQuote(BankQuoteRequest quoteRequest, OnBestQuoteEvent onBestQuoteEvent, Object ACT)
{

Message requestMessage = new Message(quoteRequest);
requestMessage.AppSpecific = aggregationCorrelationID;
requestMessage.ResponseQueue = bankReplyQueue.GetQueue();
IMessageSender[] eligibleBanks =
          connectionManager.GetEligibleBankQueues(quoteRequest.CreditScore,
quoteRequest.HistoryLength,
                                          quoteRequest.LoanAmount);
       aggregateBuffer.Add(aggregationCorrelationID,
          new BankQuoteAggregate(aggregationCorrelationID, eligibleBanks.Length,onBestQuoteEvent, ACT));
       aggregationCorrelationID++;
       MessageRouter.SendToRecipientList(requestMessage, eligibleBanks);
   }
   private void OnBankMessage(Message msg)
   {
    msg.Formatter = GetFormatter();

       BankQuoteReply replyStruct;
       try
       {
          if (msg.Body is BankQuoteReply)
          {
             replyStruct = (BankQuoteReply)msg.Body;
             int aggregationCorrelationID = msg.AppSpecific;
             Console.WriteLine("Quote {0:0.00}% {1} {2}",
                            replyStruct.InterestRate, replyStruct.QuoteID,replyStruct.ErrorCode);
             if (aggregateBuffer.Contains(aggregationCorrelationID))
             {
                 BankQuoteAggregate aggregate = (BankQuoteAggregate)(aggregateBuffer[aggregationCorrelationID]);
                 aggregate.AddMessage(replyStruct);
                 if (aggregate.IsComplete())
                 {
                    aggregate.NotifyBestResult();
                    aggregateBuffer.Remove(aggregationCorrelationID);
                 }
             }
             else
             { Console.WriteLine("Incoming bank response does not match any aggregate"); }
          } else 
         { Console.WriteLine("Illegal request."); }
      }
      catch (Exception e)
      {
         Console.WriteLine("Exception: {0}", e.ToString());
      }
} }

銀行ゲートウェイは銀行からの引用返信を受信すると、OnBankMessage?メソッドが実行されます。このメソッドは正しい型に着信メッセージを変換し、AppSpecific?プロパティによって関連するアグリゲートをみつけていく。そして、アグリゲートに新しい入札が追加されます。アグリゲートが完了したら(BankQuoteAggregate?クラスで定義されているように、BankGateway?は、呼び出し側によって提供されるデリゲートを呼び出します。

要求を受け付ける

今、私たちはよくカプセル化された信用調査ゲートウェイと銀行ゲートウェイを持っている。ローンブローカーが要求を受け入れる準備が整った。以前のセクションでは、MQServiceとAsyncRequestReplyService?のベースクラスの設計を議論した。LoanBroker?クラスは、それがすぐに応答キューに結果を送り返すことができないためAsyncRequestReplyService?から継承しますが、いくつかの非同期操作(クレジットスコアを取得し、銀行との通信)が完了した後である。

LoanBroker?を実装する最初のステップは、ローンブローカーが処理するメッセージの種類を定義することです:

public struct LoanQuoteRequest
{
   public int SSN;
   public double LoanAmount;
   public int LoanTerm;
}

public struct LoanQuoteReply
{
   public int SSN;
   public double LoanAmount;
   public double InterestRate;
   public string QuoteID;
}

次に、AsyncRequestReplyService?を継承するクラスを作成し、ProcessMessage?メソッドをオーバーライドする必要があります。

"プロセス"

着信メッセージによってトリガーされるプロセスは、どのような単一のメソッドも含んでいないため、ローンブローカーは以前のクラスとは異なります。その代わりに、プロセスの完了は外部のイベントのシーケンスに依存します。ローンブローカーは、イベントの3つのタイプを受け取ることができます。

・新しいローン要求メッセージが到着

・クレジットスコアの応答メッセージが到着(CreditBureauGateway?経由)

・銀行の引き合いメッセージが到着(BankGateway?経由)

ローンブローカーのロジックが複数のイベントハンドラに分散されているので、これらの機能を越えブローカーの状態を維持する必要があります。非同期完了トークンが入ってくるところです!信用調査ゲートウェイと銀行ゲートウェイは要求を送信するときに、呼び出し元(ローンブローカー)にオブジェクトインスタンスへの参照を渡すことができることを覚えておいてください。ゲートウェイが応答メッセージを受信したオブジェクト参照を戻す。この機能を利用するには、次のようにローンブローカーでACTを宣言する:

internal class ACT
{
   public LoanQuoteRequest loanRequest;
   public Message message;
   public ACT(LoanQuoteRequest loanRequest, Message message)
   { 
       this.loanRequest = loanRequest;
       this.message = message;
    }
}

ACTは、元の要求メッセージ(メッセージIDおよび応答メッセージを作成するために必要な返信アドレスを含む)と要求データ構造(応答メッセージにSSN、貸付金額をコピーするために必要とされる)のコピーを含む。技術的に言えば、要求メッセージからリクエスト構造体の内容を抽出できるため、ACTは重複した情報を持っている。しかし、厳密に型指定された構造体にアクセスの利便性は、いくつかの価値がある。

ローンブローカーの残りは次のように実装されます

internal class LoanBroker : AsyncRequestReplyService
{
   protected ICreditBureauGateway creditBureauInterface;
   protected BankGateway bankInterface;
   public LoanBroker(String requestQueueName,
                    String creditRequestQueueName, String creditReplyQueueName,
                    String bankReplyQueueName, BankConnectionManager
connectionManager): base(requestQueueName)
{

       bankInterface.Listen();
   }
   protected override Type GetRequestBodyType()
   {
       return typeof(LoanQuoteRequest);
   }
   protected override void ProcessMessage(Object o, Message msg)
   {
       LoanQuoteRequest quoteRequest;
       quoteRequest = (LoanQuoteRequest)o;
       CreditBureauRequest creditRequest = LoanBrokerTranslator.GetCreditBureaurequest(quoteRequest);
       ACT act = new ACT(quoteRequest, msg);
       creditBureauInterface.GetCreditScore(creditRequest, new OnCreditReplyEvent(OnCreditReply), act);
}

   private void OnCreditReply(CreditBureauReply creditReply, Object act)
   {
       ACT myAct = (ACT)act;
       Console.WriteLine("Received Credit Score -- SSN {0} Score {1} Length {2}",
                      creditReply.SSN, creditReply.CreditScore,creditReply.HistoryLength);
      BankQuoteRequest bankRequest =
         LoanBrokerTranslator.GetBankQuoteRequest(myAct.loanRequest ,creditReply);
      bankInterface.GetBestQuote(bankRequest, new OnBestQuoteEvent(OnBestQuote),act); }

creditBureauInterface = (ICreditBureauGateway)
(new CreditBureauGatewayImp(creditRequestQueueName, creditReplyQueueName));

creditBureauInterface.Listen();
bankInterface = new BankGateway(bankReplyQueueName, connectionManager);
private void OnBestQuote(BankQuoteReply bestQuote, Object act)
   {
       ACT myAct = (ACT)act;
       LoanQuoteReply quoteReply = LoanBrokerTranslator.GetLoanQuoteReply(myAct.loanRequest, bestQuote);
       Console.WriteLine("Best quote {0} {1}", quoteReply.InterestRate, quoteReply.QuoteID);
      SendReply(quoteReply, myAct.message);
   }
}

ローンブローカーは、要求を受信し、相関した応答を送信するためのサポートを提供するAsyncRequestReplyService?から継承する。ローンブローカーは、着信要求メッセージに対処するためのメソッドであるProcessMessage?をオーバーライドします。ProcessMessage?はACTの新しいインスタンスを作成し、信用調査ゲートウェイがクレジットスコアを要求するように呼び出します。興味深いことに、メソッドはそこで終了する。信用調査ゲートウェイはローンブローカーによって指定されたデリゲートを呼び出したとき、処理は続行されます。:OnCreditReply?。このメソッドは、ACTと銀行の引き合い要求を作成するための応答を使用しており、銀行のゲートウェイを要求メッセージを送信するために呼び出します。今回はコールバックデリゲートとしてメソッドOnBestQuote?を指定します。銀行ゲートウェイはすべての銀行の引き合い返信を受け取ると、デリゲートを使用してこのメ​​ソッドを呼び出し、ACTのインスタンスを渡します。 OnBestQuote?は、顧客への応答を作成するために銀行の引き合いとACTを使用し、SendReply?のベースクラスの実装を使用してそれを送信します。

おそらく、ソースコードで気づいた1つのクラスはLoanBrokerTranslator?だろう。このクラスは、異なるメッセージ·フォーマット間の変換を支援する静的メソッドのいくつかを提供します。

LoanBroker?クラスでは、設計のトレードオフを示しています。コー​​ドは、読むのが非常に容易で、メッセージングやスレッド関連する概念(AsyncRequestReplyService?から継承を除く)への参照は自由です。しかし、メイン機能の実行がデレゲートに隠れて、お互いに直接参照していない三つのメソッドに分散されます。これは、すべての外部部品を含めたトータルソリューションを考慮せずに実行の流れを理解することは困難です。

Refactoring the Loan Broker


Putting it All Together

唯一残されたパーツは、テストクライアントだ。 テストクライアント設計はクレジットカード案内所入り口に似ている。 テストクライアントは、多くの繰り返されるリクエストを特定するができ、未解決のリクエストと、次に来るレスポンスを関連づけることができる。 いったん、私達が全ての処理(銀行、クレジットカード案内、ローン仲介)を開始すると、例を実行することができる。 私達は、シンプルなメインクラスの多くを、コンソールアプリケーションとして、それぞれのコンポーネントを使用する。 私達は、画面でアクティビティが通りすぎて行くのを見る。システムを通じてメッセージの流れを指し示している。(図、参照)

Improving Performance

今、私達が、完璧なソリューションを実行し、非同期処理のソリューションと、同期処理ソリューションのスループットを比較するための、いくつかのパフォーマンスのマトリクスを集めることができる。 テストデータジェネレーターを使い、私達は、ローンブローカーに生成されたリクエストをランダムに50個送信する。 次のページに掲載している、テストデータジェネレーターは、50個の返信メッセージを受け取るために、33秒かかることを報告している。

各リクエストが、0.6秒かかっていることと考えられるだろう。 間違いだ! ローンブローカーのスループットは、33秒で、50リクエストである。しかし、 いくつかのリクエストは、完了するのに、27秒かかっている。 なぜシステムはそんなに遅いのだろうか? テスト実行中のメッセージキューのスナップショットを見てみよう。

31 Messages Are Queued Up in the Credit Request Queue

31個のメッセージが、クレジットカード案内のリクエストキューの中で、待ちになっている。 明らかに、クレジットカード案内が私達のボトルネックである。 なぜなら、全てのキューリクエストは、最初にクレジットカード案内を通らなければならないからである。 今、私達は、いくつかの疎結合の見返りを受けることができ、クレジットカード案内の2つ追加するインスタンスを開始することができる。 今、クレジットカード案内サービスの3つのパラレルインスタンスを実行させる。 これは、私達のボトルネックを修正すべきではないか?

50メッセージすべてを処理するための全体の時間は、最も長いリクエストは、6秒のレスポンスを待っていて、 21秒に下がている。 平均で、クライアントは、オリジナルバージョンの半分の8.63秒ローンのリクエストに返信するのに待たなければならない。

ボトルネックをなくしたように見受けられるが、 メッセージのスループットは私達が希望していたものほど、劇的に上がっていない。 この単純な例は私達はすべての処理を1つのCPUで実行しているので、すべての処理を同じリソースで完了させていることを、覚えておいてほしい。 クレジットカード案内のボトルネックが事実上、適切であるかを検証するために、新しいキューの統計を見てみよう。

Now Bank 5 Appears to Be a Bottleneck

私達は、ちょうど新しいボトルネックを見つけるために、1つのボトルネックを取り除いたようだ。 銀行5。なぜ銀行5なのか? 銀行5は、質屋である。 みんなにローンを提供する。 それで、銀行5は、ほぼすべての見積りのリクエストの部分である。 私達は、今、銀行5の複数のインスタンスを開始しようとすることができた。 だが、質屋がスループットを良くするために複数のインスタンスを実行することを予測するのは、現実的ではない。 私達の他のオプションは、銀行のリクエストのために経路を決めるロジックを変更することである。 それ以来、質屋は他の銀行を超えて価値のあるプレミアムを請求するため、これらのケースにおいてのみ、他の銀行は見積がなく、その見積は最も低い見積もりになる傾向がある。 この観察記録をよく考えると、もしその見積もまた他の銀行で提供していたなら私達は質屋へのリクエストの経路を決めなくてもシステムの効率を改善することができる。 この料金は、システムの全てのふるまいに影響はないだろう。

私達は、これらのいくつかの他の銀行によって提供できない見積依頼のためだけにBank 5を含めるために、BankConnectionManager?を変更する。 BanConnectionManager?の修正は、下の通り。

internal class BankConnectionManager
{ 
    static protected BankConnection[] banks = { new Bank1(), new Bank2(), new Bank3(), new Bank4() };
  static protected BankConnection catchAll = new Bank5();

   public IMessageSender[] GetEligibleBankQueues(int CreditScore, int HistoryLength, int LoanAmount)
   {
       ArrayList lenders = new ArrayList();
       for (int index = 0; index < banks.Length; index++) {
          if (banks[index].CanHandleLoanRequest(CreditScore, HistoryLength, LoanAmount)) 
              lenders.Add(banks[index].Queue);
       }
       if (lenders.Count == 0)
          lenders.Add(catchAll.Queue);  

       IMessageSender[] lenderArray = (IMessageSender [])Array.CreateInstance (typeof(IMessageSender), lenders.Count);
       lenders.CopyTo(lenderArray);
       return lenderArray;
    }
}

修正したコードで実行することは、下の図を表示する結果を生み出した。

Sending 50 Quote Requests Using Three Credit Bureau Instances and a Modified BankConnectionManager?

テストは、今、全ての50リクエストが、もともとの時間の半分の、12秒で処理された結果をもたらした。 より重要なことに、ローン相場のリクエストを提供するための平均の時間は、今や4秒以内であり、最初のバージョンから4倍改善した。 この例は、'Recipient List'を利用して予測できるルーティングの利点を表している。 なぜなら、ローン仲介人は、ルーティングを越えて管理を持てる。

私達は、どのくらいの’知識’で、私達が、外部の部分にいくつかの変更を必要とせずに、ルーティングロジックを組み立てることができるか、決めることができる。

トレードオフは、ローンブローカーがだんだん内部の部分についての知識に依存するようになることである。 例えば、オリジナルの’’BankConnectionManager?’'は、全ての銀行を同じものとして扱う一方で、 修正されたバージョンは、銀行5は、他のオプションがない場合のみで契約されるべき捕まえた全てのプロバイダーである事実に頼ることになる。 もし銀行5が、よりよいレートの提供を開始したら、クライアントは最も見込みのある契約をしないだろう。

A Few Words on Testing

ローンブローカーの例は、配布や、非同期や、イベントドリブンになっていくことで、どういったシンプルなアプリケーションが適切に複雑になることができるかを、みせた。 私達は、1ダースのクラスと、非同期でのメッセージ処理のイベントドリブンの性質を扱うために、デリゲートを使う。 増えた複雑さもまた、欠陥のリスクを増やすことを意味する。 非同期の性質は、特定の一時的な条件に依存するため、これらの欠陥を再現したり、トラブルシュートするのが難しい。 これらの追加の理由としては、メッセージングソリューションは、テストするアプローチが全体的に必要となる。 私達は、メッセージングソリューションのテストで、全ての本を書くことができる。 しかし、今、いくつかのシンプルさ、テストのために実施可能なアドバイス、を含めたい。 次の3つのルールで要約する。

  • インターフェイスと実装クラスを利用して、アプリケーションをメッセージング実装から隔離する。
  • メッセージングの環境に差し込む前に、ユニットテストケースとビジネスロジックをテストする。
  • 同期的にテストができるようにメッセージングレイヤのモックの実装を提供する。

Isolate the Application from the Messaging Implementation

public interface ICreditBureauGateway
{
    void GetCreditScore(CreditBureauRequest quoteRequest,OnCreditReplyEvent OnCreditResponse, Object ACT);
    void Listen();
}
public class MockCreditBureauGatewayImp : ICreditBureauGateway
{
     private Random random = new Random();
     public MockCreditBureauGatewayImp()
     { }

     public void GetCreditScore(CreditBureauRequest quoteRequest, OnCreditReplyEvent OnCreditResponse, Object ACT)
     {
          CreditBureauReply  reply = new CreditBureauReply();
          reply.CreditScore =  (int)(random.Next(600) + 300);
          reply.HistoryLength = (int)(random.Next(19) + 1);
          reply.SSN = quoteRequest.SSN;
        OnCreditResponse(reply, ACT);
     }

     public void Listen()
     { }
}

Test the Business Logic with Unit Test Cases


Provide a Mock Implementation of the Messaging Layer

public class MockQueue: IMessageSender, IMessageReceiver
{
     private OnMsgEvent onMsg = new OnMsgEvent(DoNothing);
     public void Send(Message msg){
         onMsg(msg);
     }

     private static void DoNothing(Message msg){

     }

     public OnMsgEvent OnMessage
     {
         get { return onMsg; }
         set { onMsg = value; }
     }

     public void Begin()
     {

     }

     public MessageQueue GetQueue()
     {
         return null;
     }
}

Limitations of This Example

Sammary

このチャプターでは、非同期メッセージキューとMSMQを利用して、ローンブローカーアプリケーションの実装を見てきた。 私達は、非同期メッセージングアプリケーションを組み立てらている継承している本当の問題を持ち出すために、実装の詳細を見せることから意図的に臆病になっていなかった。 私達は、ベンダー仕様のメッセージングAPIより設計のトレードオフにフォーカスしたので、例もまたC#の開発者ではなくても価値のあるものになっている。

この例によって、私達がは単純なメッセージングアプリケーションさえ実装が複雑であることを思い出す。 しっかりしたアプリケーションであることを当然と見なすことができる多くのことは、非同期メッセージングを使ったとき、かなりのコーディングの努力の量を要求することができる。 幸運にも、デザインパターンは、、徹底的にベンダーの専門用語に陥ることなく、いくつかのデザインのトレードオフを述べるために言語と一緒で私達に提供されている。