EIP / Messaging Mapper


Messaging Mapper

一言要約

メッセージングインフラストラクチャとドメインオブジェクト間で、それらのイベントを監視し、相互のデータ交換を担当するMesseging Mapperを作成する。このパターンにより、メッセージングインフラストラクチャとドメインオブジェクトも双方の存在を知ることなく、データを交換することが出来る。

要約

メッセージングを使用してアプリケーションを統合する場合、メッセージ内のデータは、多くの場合、統合されたアプリケーション内部のドメインオブジェクトから導出されます。ドキュメントメッセージを使用している場合は、メッセージそれ自体が直接ドメインオブジェクトを表すかもしれない。また、コマンドメッセージを使用する場合は、コマンドに関連付けられたデータフィールドの一部だけでなく、ドメインオブジェクトから抽出される可能性がある。

メッセージとオブジェクトとの間にはいくつかの明確な違いがあります。たとえば、ほとんどのオブジェクトは、オブジェクト参照と継承関係の関連に基いています。多くのメッセージングインフラストラクチャーは、さまざまなアプリケーションと通信できるようにしなければならないため、これらの概念をサポートしていない(そのうちのいくつかは、まったくオブジェクト指向でないかもしれません)。

どのように、お互いの独立を維持しながら、ドメインオブジェクトとメッセージングインフラストラクチャ間でデータを移動するのか?

なぜメッセージが全くのドメインオブジェクトのように見えるようにすることを、問題にするのか?(訳者:うまく訳せなかった→and make the problem go away?)それは正規データモデルまたは共通のメッセージング規格(例えば、ebXMLの)によって定義されているために、多くの場合でメッセージフォーマットをコントロールできません。確かに、まだドメインオブジェクトに対応する形式でメッセージを公開し、共通のメッセージ形式に必要な変換を行うために、メッセージング層の内側にMessage Translatorを使用することができます。このアプローチは、一般的にアプリケーション内で変換を許可していないサードパーティのシステム(例えば、データベースアダプタ)へのアダプターによって使用されます。

代替として、ドメインはMessage Translatorを必要とせずに必要な形式でメッセージを作成し、公開することができます。 このオプションは、中間のメッセージを公開していないため、優れたパフォーマンスとなります。 また、ドメインモデルに多数の小さなオブジェクトが含まれている場合には、ルーティングを簡素化し、メッセージングレイヤの内部の効率を改善するために、単一のメッセージにそれらを結合することが有益かもしれません。追加の変換ステップを追加する余裕がある場合であっても、ドメインオブジェクトを模倣するメッセージを作成したい場合には限界に突き当たります。このアプローチの欠点は、ドメインがメッセージの形式を、知っている必要があるということで、複数のフォーマットが使用されるか、または形式が変更された場合、ドメインのメンテナンスがが困難になることです。

ほとんどのメッセージングインフラストラクチャは、アプリケーションプログラミングインタフェース(API)の一部として、 "メッセージ"オブジェクトの概念をサポートする。このメッセージオブジェクトは、チャネルを介して送信するデータをカプセル化します。ほとんどの場合、このメッセージオブジェクトは、文字列、数値、または日付のようなスカラーデータ型を含めることができますが、継承やオブジェクトの参照をサポートしていません。これは、RPCスタイルの通信(すなわちRMI)と、非同期メッセージングシステムの主な相違点の一つです。コンポーネントへのオブジェクト参照を含む非同期メッセージを送信すると仮定しましょう。メッセージを処理するために、コンポーネントはオブジェクト参照を解決しなければなりません。メッセージソースからオブジェクトを要求することによってこれを行うでしょう。しかし、要求 - 応答の相互作用は、そもそもコンポーネント間の疎結合といった、非同期メッセージングを使用する動機のいくつかをダメにするでしょう。さらに悪いことに、非同期メッセージがサブスクライバによって受信された時点で、参照されるオブジェクトは、もはやソースシステムに存在しないかもしれない。

オブジェクト参照の問題を解決する一つの試みは、オブジェクトの依存関係ツリーをトラバースし、メッセージ内にすべての依存オブジェクトを含めることです。たとえば、注文オブジェクトの参照が5つの注文項目のオブジェクトを持っているならば、メッセージ内に5つのオブジェクトを含んでいるでしょう。これは、受信者が "ルート"オブジェクトによって、すべてのデータの参照へのアクセスすることができることを保証します。多くのオブジェクトが相互に関連しているきめの細かいドメインオブジェクトモデルを使用している場合、メッセージはすぐにサイズが爆発します。メッセージに何が含まれ何が含まれないかを、より詳細に制御することが望ましいでしょう。

私たちのドメインオブジェクトは、自己完結型であり、他のオブジェクトへの参照を持っていないと仮定しましょう。メッセージングインフラストラクチャは、言語に依存しない(JMSインタフェース、ObjectMessage?、NETのSystem.Messagingなどのメッセージングシステム)ようにしなければならないため、まだ簡単にはメッセージにドメインオブジェクト全体を貼り付けることはできません。私たちは、シリアル化を、オブジェクトを文字列することと考えることができ、そして、それはほとんどすべてのメッセージングシステムでサポートされている "データ"と呼ばれる文字列フィールドにそれを格納することができます。しかし、このアプローチは同様の欠点をもっています。まず、この文字列フィールドは、メッセージング層に '不透明'になるので、メッセージルーターは、ルーティングの目的のためにオブジェクトのプロパティを使用することができないでしょう。そしてまた、'データ'フィールドの内容を解読しなければならないので、テストとデバッグが困難になるでしょう。また、彼らは単一の文字列フィールドを含むように、すべてのメッセージを構築すると、インフラストラクチャへのすべてのメッセージが同じように見えるので、メッセージタイプによってメッセージをルーティングことができない。また、メッセージングインフラストラクチャは、 "データ"フィールド内部を何も検証しないので、メッセージの正しい形式を確認するのは難しいだろう。最後に、これらの表現は、通常言語間での互換性はないので、言語のランタイムライブラリが提供するシリアライズ機能は使用することができないだろう。それで我々は自分自身のシリアル化コードを記述しなければならないでしょう。

一部のメッセージングインフラは今は、メッセージ内部のXMLフィールドをサポートするので、XMLにオブジェクトをシリアライズことができる。これは、メッセージは解読が容易であり、メッセージングレイヤが直接内部の要素とXMLの文字列へアクセスすることができるので、欠点の一部を軽減することができます。しかし、我々は、かなり冗長なメッセージと限られたデータ型の検証を扱う必要があります。(訳注:よく意味が分からなかった)さらに、我々はまだ、オブジェクトをXMLに変換および逆変換するコードを作成する必要があります。

使用するプログラミング言語によって、リフレクションをサポートしていない古い言語を使用する場合は特に、非常に複雑になる可能性があります。我々はいくつかの理由で、ドメインオブジェクトからこのマッピングコードを分離することをお勧めします。まず第一に、アプリケーションロジックと低レベルの言語機能のコードを混ぜ合わせたくない。多くの場合、ドメインロジックに焦点を当てるグループとは別に、メッセージング層での作業に専念するプログラマーのグループを持つことになります。1つのオブジェクトにコードの両方の部分を貼り付けると、チームが並行して作業することが困難になるでしょう。第二に、マッピングコードはメッセージングAPI(Messageオブジェクトをインスタンス化する場合など)に呼び出しを行う必要があるため、ドメインオブジェクト内にマッピングコードを組み込むと、メッセージングインフラストラクチャに依存したドメインオブジェクトが作成されます。これは、メッセージングを使用するか、別のベンダーのメッセージングインフラストラクチャを使用していない別のコンテキスト内のドメインオブジェクトの再使用を阻んでしまうため、ほとんどのケースではこの依存性は望ましくありません。結果として、ドメインオブジェクトの再利用性を妨げます。

私たちはしばしば、人々が効果的にメッセージングAPIからメッセージング独立させる、 メッセージングインフラストラクチャAPIをラップする"抽象化レイヤ"コードを書いているのを見ることがある。それはメッセージングの実装からメッセージングインタフェースを分離するため、このような層は、間接的なレベルを提供します。そうして、他のベンダーのメッセージング層に切り替える必要のある場合でも、メッセージング関連のコードを再利用することができます。私たちがやらなければならないことは、新しいAPIへのメッセージングインターフェイスを変換する新しい抽象化レイヤを実装することです。しかしながら、このアプローチは、メッセージングレイヤ上のドメインオブジェクトの依存関係を解決しない。ドメインオブジェクトは、ベンダー固有のメッセージングAPIとは対照的に、抽象化メッセージングインターフェースへの参照を含んでいるでしょう。しかし、我々はまだメッセージングを使用していないコンテキストでドメインオブジェクトを使用することはできません。

多くのメッセージは、複数のドメインオブジェクトから構成されています。メッセージングインフラストラクチャを介してオブジェクト参照を渡すことはできませんので、他のオブジェクトからのフィールドを含める必要がある可能性があります。いくつかのケースでは、一つのメッセージ内にすべての依存オブジェクト全体の "依存関係ツリー"を含んでいてもよい。

どのクラスがマッピングコードを保持する必要があるか?

メッセージングインフラストラクチャとドメインオブジェクトの間のマッピングロジックが含まれた、分離されたMesseging Mapperを作成します。オブジェクトもインフラいずれもメッセージングマッパーの存在の知識を持っていない。

Diagram 1> 

メッセージングマッパーは、1つ以上のドメインオブジェクトにアクセスし、メッセージングチャネルが必要とするメッセージに変換する。また、受信したメッセージに基づいてドメインオブジェクトの作成または更新といった、反対の機能を実行します。Messaging Mapperはドメインオブジェクト(群)とメッセージング層を参照する別のクラスとして実装されているので、どちらの層も他を認識していません。層もMessaging Mapperを知らない。

メッセージングマッパーは[EAA]で定義されたマッパーパターンを特化したものです。これは、この本の中で定義されたデータマッパーといくつかの類似点を共有しています。マッピング戦略にOR(オブジェクトリレーショナル)を使用している人は、別のパラダイムを使う層の間のマッピングデータの複雑さを理解しているだろう。生来のメッセージングマッパーの問題は、同様に複雑であり、すべての可能な側面の詳細については、本書の範囲を超えています。データソースのアーキテクチャパターンの多くは[EAA]メッセージングマッパー層を作成する型ににとても良い読み物になります。

Messaging MapperはメッセージングAPIを包む抽象化レイヤで頻繁に使用される概念とは異なっています。抽象化レイヤの場合には、ドメインオブジェクトは、メッセージングAPIを知りませんが、彼らは抽象化レイヤについては知っています(抽象化レイアは本質的に、ゲートウェイの機能を実行する([EAA]参照))。メッセージングマッパーの場合、オブジェクトは、メッセージングを扱っていることが全く見当がつかない。

メッセージングマッパーの目的は、要素を分離するために使用されるメディエータ([GoFの】)と同様である。(ただし、)メディエータの場合、要素はメディエータを認識しているのに対し、どちらの要素もメッセージングマッパーを認識していない。

ドメインオブジェクトも、メッセージングインフラのいずれもメッセージングマッパーについて知っていないならば、それはどのように呼び出されるのでしょうか?ほとんどの場合、Messaging Mapperは、メッセージングインフラストラクチャまたはアプリケーションのいずれかによってトリガされるイベントを通じて呼び出される。どちらも、メッセージングマッパーに依存していないので、イベント通知は、コードの別の部分またはメッセージングマッパーをObserverパターン([GoFの]を参照)にすることによって発生させることができます。例えば、メッセージングインフラストラクチャとのインターフェイスにJMS APIを使用する場合、すべての着信メッセージが通知されるようにMessageListener?インタフェースを実装することができます。同様に、ドメインオブジェクト内の任意の関連するイベントが通知されるように、そしてMessaging Mapperを起動するように、オブザーバーを使用することができる。もしも、アプリケーションから直接メッセージングマッパーを起動する必要がある場合、アプリケーションが、メッセージングマッパーの実装に依存しないように、メッセージングのマッパーインターフェースを定義する必要があります。

コーディングの負担を軽減

いくつかのメッセージングマッパーの実装は多くの繰り返しコードが含まれている場合があります。(ドメインオブジェクトからフィールドを取得し、メッセージオブジェクトに格納する。)次のフィールドへ移動し、すべてのフィールドが終了するまで繰り返す。これはかなり面倒であるし、またコードの重複のように怪しいにおいがする。この退屈を回避するためのいくつかのツールがあります。まず、汎用的な方法で、ドメインオブジェクトからフィールドを抽出するためにリフレクションを使用して汎用的なメッセージングマッパーを書くことができます。例えば、ドメインオブジェクト内のすべてのフィールドのリストをトラバースことができ、メッセージオブジェクトの同じ名前のフィールドに格納することができます。明らかに、フィールド名が一致する場合のみ、これは動作します。我々の以前の議論によると、メッセージオブジェクトでそれらを格納することはできませんので、オブジェクト参照を解決するためのいくつかの方法を考え出す必要がある。代替手段は、Messaging Mapperコードを生成するために、設定可能なコードジェネレータを使用することである。これは、フィールドの命名をより柔軟にすることを可能にします(メッセージフィールド名とドメインオブジェクトフィールド名が一致する必要がなく、オブジェクト参照に対処するための巧妙な方法)。コードジェネレータの欠点は、彼らがテストし、デバッグが困難になることができるということですが、我々は十分にそれを汎用的にする場合、一度だけ、それを記述する必要があります。たとえば、Microsoft.NETのようなフレームワークは、オブジェクトのXMLへのシリアライゼーションとその逆変換そしてオブジェクトのシリアル化に関わる面倒な作業の多くを削減する機能をもつ。フレームワークがメッセージにオブジェクトを変換をした場合でも、この変換は、 構文レベルの変換に制限される。すべての作業をフレームワークだけにさせるのは魅力的かもしれないが、ドメインオブジェクトに一対一に対応したメッセージを作成するだけだろう。先ほど説明したように、メッセージの制約および設計基準は、ドメインオブジェクトのものとは全く異なるので、これは望ましくないかもしれない。 それは、希望のメッセージの構造に対応するインタフェースオブジェクトのセットを定義し、そして、フレームワークにメッセージと、これらのオブジェクトの間の変換を実行させる、意味があるかもしれません。メッセージングマッパー層は、真のドメインオブジェクトとインタフェースオブジェクト間の変換を管理します。これらのインタフェースオブジェクトは、データ転送オブジェクト([EAA])にいくつかの類似点をも持つが動機が少し異なっている。

Mapper 対 Translator

Messaging Mapperを使用している場合でも、それはまだ正規のデータモデルに準拠したメッセージにMessaging Mapperによって生成されたメッセージを変換するために、Message Translatorを使用することは理にかなっています。これは、私たちに間接のレベルを提供します。私たちは、このようなオブジェクトの参照とデータ型変換などの問題を解決し、メッセージング層内部のMessage Translatorに構造マッピングを残すために、メッセージングマッパーを使用することができます。この追加の疎結合のために支払うプライスは、追加コンポーネントの作成と些細なパフォーマンスペナルティです。また、時にはそれが統合ベンダーが提供する、ドラッグ&ドロップする'doodleware'のに対して、アプリケーションのプログラミング言語の内部で複雑な変換を実行する方が簡単なこともあります。

Messaging MapperとMessage Translatorの両方を使用する場合、標準的なデータ形式とドメインオブジェクト間の間接のレベルを得ることができます。「コンピュータ科学は、すべての問題は間接のひとつ以上のレベルを追加することで解決できる分野である」と言われています。このルールは、ここに当てはまるのでしょうか?(訳注:この部分はどのように理解しているのかみんなに聞いてみたい。)追加の間接はアプリケーションコードに触れることなく、メッセージング層内部の規範的モデルの変化を補償する機能を提供します。それはまた、この種の作業に最適化されているメッセージング層のマッピングツールに退屈なフィールドマッピングとデータ型の変更(例えば、英数字 'POSTAL_CODE'フィールドに数値 'ZIP_CODE'フィールド)を残すことによって、アプリケーション内のマッピングロジックを簡素化することができます。Messaging Mapperはその後、主にオブジェクト参照の解明と不要なドメインオブジェクトの詳細の除去に対処するだろう。間接の余分なレベルの見かけの欠点は、ドメインオブジェクトの変化がMessage MapperとMessage Translatorの両方の変更が必要になるかもしれないということです。Message Mapperコードを生成するように管理した場合、この問題は、概して消えていく。


マッパーとメッセージ翻訳を組み合わせる<Diagram 2> 

例:JMSメッセージングでマッパー 

アグリゲータJMS例の、AuctionAggregate?クラスは、JMSメッセージングシステムと入札(Bid)クラス間のメッセージングマッパーとして機能します。addMessageとgetResultMessage?メソッドはJMSメッセージと入札オブジェクト間の変換を行う。メッセージングシステムでも入札クラスどちらもこの相互作用の知識を持っていない。

関連パターン: Aggregator, Canonical Data Model, Command Message, Document Message, Message Router, Message Translator

担当者のつぶやき

みんなの突っ込み