クライアント側とサーバー側の両方で DTO をドメイン エンティティにマッピングする必要がありますか?
-
18-09-2019 - |
質問
私は豊富なドメイン モデルを持っています。ほとんどのクラスには、計算されるか、メンバー オブジェクトのプロパティを公開する (つまり、これらのプロパティの値は決して永続化されません) いくつかの動作といくつかのプロパティがあります。
私のクライアントは WCF 経由でのみサーバーと通信します。
そのため、各ドメイン エンティティに対して、対応する DTO (データのみを含む単純な表現) と、以下を実装するマッパー クラスがあります。 DtoMapper<DTO,Entity>
また、静的ゲートウェイを介してエンティティを同等の DTO に、またはその逆に変換できます。
var employee = Map<Employee>.from_dto<EmployeeDto>();
このアプリケーションのサーバー側は主に永続性に関するもので、DTO は WCF サービスから受信し、逆シリアル化されてから、任意の ORM によってデータベースに永続化されます。または、クエリ要求が WCF から受信され、ORM がそのクエリを実行します。 DB にアクセスし、シリアル化されて WCF によって送り返されるオブジェクトを返します。
このシナリオを考えると、 永続ストアをドメイン エンティティにマップすることに意味はありますか、それとも DTO に直接マップするだけでよいでしょうか?
ドメイン エンティティを使用する場合、フローは次のようになります。
- クライアントリクエストオブジェクト
- WCF はサーバーに要求を送信します
- ORM はデータベースにクエリを実行し、ドメイン エンティティを返します
- マッパーによって DTO に変換されたドメイン エンティティ
- WCF は DTO をシリアル化してクライアントに返します
- クライアントが DTO を逆シリアル化する
- マッパーによって DTO がドメイン エンティティに変換される
- 作成されたビューモデル、 等
復路でも同様
DTO に直接マッピングすると、オブジェクトごと、リクエストごとに 1 つのマッピングを削除できます。これを行うことで何を失うでしょうか?
唯一思い浮かぶのは、挿入/更新の前に別の検証の機会を設けることです。なぜなら、DTO が検証の対象になったことがあるか、ネットワーク経由で送信される前にドメイン エンティティとして存在していたという保証がないからです。選択時に検証します (別のプロセスがデータベースに無効な値を入れた可能性がある場合)。他に理由はありますか?これらの理由は、追加のマッピング手順を正当化するのに十分ですか?
編集:
上で「任意の ORM」と言いましたが、できる限り ORM と永続性に依存しないようにしたいと思っていますが、NHibernate に特有の特別な追加事項がある場合は、ぜひ追加してください。
解決
個人的には、マッピングをサーバー側に保持しておくことをお勧めします。おそらく、現在のデザインに至るまでに多くの作業を行ってきたことでしょう。それを捨てないでください。
Web サービスとは何かを考えてみましょう。これは単に ORM を抽象化したものではありません。それは 契約. 。これは、内部と外部の両方のクライアントのためのパブリック API です。
パブリック API には、変更する理由がほとんどないはずです。新しい型やメソッドの追加を除く、API へのほとんどすべての変更は破壊的な変更です。ただし、ドメイン モデルはそれほど厳密ではありません。新しい機能を追加したり、元の設計の欠陥を発見したりした場合には、時々変更する必要があります。内部モデルへの変更によって、サービスのコントラクトを通じて連鎖的な変更が発生しないようにしたいと考えています。
実際には、特定の Request
そして Response
同様の理由で各メッセージのクラスを作成します。破壊的な変更を加えることなく、既存のサービスやメソッドの機能を拡張することがはるかに簡単になります。
クライアントはおそらくそうではありません 欲しい サービス内で内部的に使用しているものとまったく同じモデル。あなたが唯一のクライアントであれば、これは透明に見えるかもしれませんが、外部クライアントがいて、そのクライアントによるシステムの解釈がどれほど乖離していることがよくあるのかを経験したことがあるなら、完璧なモデルを漏洩させないことの価値が理解できるでしょう。サービス API の範囲外です。
そして時々、それは均等ではない 可能 API を通じてモデルを送り返します。これが発生する理由は数多くあります。
オブジェクト グラフ内のサイクル。OOP ではまったく問題ありません。連載では悲惨。結局、グラフをどの「方向」でシリアル化する必要があるかについて、面倒な永続的な選択をしなければならないことになります。一方、DTO を使用すると、当面のタスクに合わせて、希望する方向にシリアル化できます。
SOAP/REST を介して特定の種類の継承メカニズムを使用しようとすると、ひいき目に見ても面倒な作業になる可能性があります。古いスタイルの XML シリアライザーは少なくともサポートしています
xs:choice
;DataContract
そうではありません。理論的根拠について屁理屈を言うつもりはありませんが、リッチ ドメイン モデルにはおそらくポリモーフィズムがあり、それを Web サービスを通じてチャネル化するのはほぼ不可能であるとだけ言えば十分です。遅延/遅延読み込み。ORM を使用する場合は、おそらくこれを利用します。適切にシリアル化されていることを確認するのは非常に厄介です。たとえば、Linq to SQL エンティティを使用すると、WCF は遅延ローダーをトリガーすることさえせず、単に
null
手動でロードしない限り、そのフィールドにデータが読み込まれますが、データが戻ってくると問題はさらに悪化します。のような単純なものList<T>
コンストラクターで初期化される自動プロパティ (ドメイン モデルではよくあること) は、コンストラクターを呼び出さないため、WCF では機能しません。代わりに、[OnDeserializing]
イニシャライザメソッドとあなた 本当に ドメイン モデルをこのようなゴミで乱雑にしたくありません。NHibernate を使用しているという括弧内の発言にも今気づきました。次のようなインターフェイスがあると考えてください
IList<T>
Webサービス上では一切連載できません!私たちのほとんどがそうしているように、NHibernate で POCO クラスを使用する場合、これはまったく機能しません。
また、内部ドメイン モデルがクライアントのニーズと単純に一致せず、それらのニーズに対応するためにドメイン モデルを変更するのは無意味である場合も多くあるでしょう。この例として、請求書のような単純なものを考えてみましょう。以下を示す必要があります。
- 口座に関する情報(口座番号、氏名など)
- 請求書固有のデータ (請求書番号、日付、期限など)
- A/R レベルの情報 (以前の残高、延滞料金、新しい残高)
- 請求書に記載されているすべての製品またはサービス情報。
- 等。
これはおそらくドメイン モデルにうまく適合します。しかし、クライアントがこれらの請求書のうち 1,200 件を表示するレポートを実行したい場合はどうすればよいでしょうか?和解報告のようなものでしょうか?
これは連載としては最悪だ。今、あなたは 1200 件の請求書を送信しています。 同じ データは何度もシリアル化されます - 同じアカウント、同じ製品、同じ A/R。内部的には、アプリケーションはすべてのリンクを追跡しています。請求書 #35 と請求書 #45 は同じ顧客宛てであることがわかっているため、請求書を共有します。 Customer
参照;この情報はすべてシリアル化時に失われ、膨大な量の冗長データを送信することになります。
本当に必要なのは、次の内容を含むカスタム レポートを送信することです。
- レポートに含まれるすべてのアカウントとそのA/R。
- レポートに含まれるすべての製品。
- 製品 ID とアカウント ID のみを含むすべての請求書。
大規模な冗長性を回避したい場合は、送信データをクライアントに送信する前に、追加の「正規化」を実行する必要があります。これは、DTO アプローチに非常に有利です。ドメイン モデルにこの構造を含めることは意味がありません。 すでに 独自の方法で冗長性を処理します。
これらが、ドメイン <--> サービス契約からのマッピングをそのまま維持することを納得させるのに十分な例と十分な根拠であることを願っています。これまでのところ、あなたは完全に正しいことをしており、素晴らしいデザインを持っています。後で大きな問題を引き起こす可能性のあるものを優先して、その努力をすべて無効にするのは残念です。
他のヒント
あなたは対称性のために、サーバ側での逆写像を作るために良いです、とにかく、クライアント側でのDTOをマップする必要があり、そうします。この方法は、あなたが十分に分離抽象化層にコンバージョンを分離します。
抽象化層だけでなく、検証のために良いですが、それ以上/以下の変更からコードを隔離し、あなたのコードがよりテスト可能と少ない繰り返して行うこと。
あなたは余分な変換での素晴らしいパフォーマンスのボトルネックを気づかない限り、また、覚えておいてください:早期の最適化は諸悪の根源です。 :)
ドメイン エンティティは DTO とは別の懸念事項であるため、必ず分離してください。DTO は通常、包括的で自己記述的なモデルですが、一方でドメイン エンティティはビジネス ロジックをカプセル化し、多くの動作が関連付けられています。
そうは言っても、追加のマッピングがどこにあるのかわかりません。ORM (別名ドメイン エンティティ) を使用してデータを取得し、それらのオブジェクトを DTO にマッピングすると、マッピングは 1 つだけになりますか?ところで、まだ次のようなものを使用していない場合は、 オートマッパー 面倒なマッピングを代わりに実行します。
これらの同じ DTO はクライアント上で逆シリアル化され、そこから UIViewModel に直接マッピングできます。全体像は次のようになります。
- クライアントは、WCF サービスからの ID によってエンティティを要求します
- WCF サービスはリポジトリ/ORM からエンティティを取得します
- AutoMapper を使用してエンティティから DTO にマッピングします
- クライアントは DTO を受け取ります
- AutoMapper を使用して UI ViewModel にマッピングします
- UIViewModelはGUIにバインドされています
、私はこれを考える重要なことだと思います。それが受け取るか、あなたのWCFサービスは、純粋に自分のドメインモデルとデータストアとの間のゲートウェイとして機能しないデータの周りにいくつかのインテリジェンスを必要とするサーバー側のドメインモデルは本当にありますか?
また、あなたのDTOがクライアントのドメインのために設計されているかどうかを検討してください。
これはあなたのサービスを介してそのデータストアにアクセスする必要がある唯一のクライアントのドメインですか?
サーバー側ののDTOは、柔軟なまたは粗粒異なるアプリケーションドメインにサービスを提供するのに十分か?
そうでない場合、それはおそらく、抽象化、外部インタフェースの実装を維持するための努力の価値があります。
(DB-> ORM-> EmployeeEntity-> Client1DTOAssembler-> Client1EmployeeDTO)。
我々は、WCFサービスは、主に、永続データ・ストアへのゲートウェイとして動作する同様のアプリケーションを有している。
私たちのケースでは、私たちのクライアントとサーバが含まアセンブリ再利用しない「のDTOを。」これは私たちに、単純にサービス参照によって生成された部分クラスにコードを追加する機会を与えてくれますので、私たちは、多くの場合、クライアント側でそのままDTOを使用して、ドメインオブジェクトとして扱うことができます。他の回我々は、WCFサービスから得た永続オブジェクトの束へのファサードとして機能し、クライアント側のみのドメインオブジェクトを有することができる。
あなたが本当にあなたのクライアントとサーバの間で、あなたのドメインオブジェクトが重複があるどのくらい、持って行動して計算されたプロパティについて考えるとき?私たちのケースでは、クライアントとサーバの間の役割分担は、クライアントとサーバーの両方に存在する(とまったく同じ)にする必要はほとんど、もしあれば、コードがあったことを意味することを決定します。
あなたの目標は完全に永続とらわれないままにしておく場合は、直接あなたの質問に答えるために、私は確かにあなたのドメインオブジェクトに永続化ストアをマッピングしてからのDTOにマップします。あなたのオブジェクトに出血し、WCFのDTOとしてそれらを使用して複雑にする可能性があまりにも多くの永続性の実装があります。
あなただけ飾ったりのDTOを強化し、それはかなりシンプルなソリューションだことができれば、クライアント側では、それは追加のマッピングを行う必要がないかもしれません。
あなたのアーキテクチャはかなりよく考えられているようです。私の腸感覚では、すでにWCFを介してそれらを送信するためにDTOのにオブジェクトを削減することを決定した場合、で、あなたは現在、サーバー側に追加のオブジェクト機能の必要性を持っていない、なぜ簡単で、マップ物事を保持しませんDTOへ直接ご永続ストアます。
あなたは何を失うのか?私はあなたが本当に何かを失うことはないと思います。あなたは、アーキテクチャは、クリーンでシンプルですね。あなたは、サーバー側での豊富な機能のための新たなニーズがあることは、将来的に決定した場合、あなたは常に再要因をすることができ、その時点でそこにあなたのドメインエンティティを再作成します。
私は後で必要に応じて、シンプルで再要因、それを維持したい、など、事前に成熟した最適化の事を避けるようにしてくださいます。