私は最近、ソリューション全体でDTOを使用していた内部プロジェクトに遭遇し、JSON RESTエンドポイントを使用して新しいMVCコントローラーを実装するために座ったときに、代わりに匿名オブジェクト規則を使用することにしましたこのAPIエンドポイントのためだけに個別のDTOクラスを宣言する方法。
public JsonResult GetSomeSuch()
{
// .. do work
return Json(new {
PropA = domainObj1.PropA,
PropB = domainObj2.PropB
});
}
..チーム大会とは対照的に、..
public JsonResult GetSomeSuch()
{
// do work ..
return Json(new SomeSuchDTO2 {
PropA = domainObj1.PropA,
PropB = domainObj2.PropB
}
}
// in some other file off in some other project and folder
public class SomeSuchDTO2 {
public int PropA { get; set; }
public string PropB { get; set; }
}
ここではDTOを使用するべきだったことを別にしておくと、それはすでにチームの慣習であったため、チームの慣習がチームのリーダーシップによって変わるまでは、チームの慣習はベストプラクティスよりも優先されます。私はソーシャル/チームの動的および一貫性の側面については心配していません。C#のアクションがJavascriptにシリアル化される具体的な時点で、その会社から移行したため、詳細の技術的価値はそれほど重要ではありません。
チームの慣例では、DTOのみを返すことであり、DTOは完全に異なるプロジェクト(ソリューション内の約50のプロジェクト)で管理されていました。この場合、そのパターンを永続化することは過度であり、維持するのが難しいと感じました。さらに、私の信念は、DRYはYAGNIと相関する必要があります。同じことを2回繰り返す場合にのみリファクタリングします。そのため、同じリターンオブジェクト構造が反映する場合にのみ、DTOを使用するようにリファクタリングします。同じコンテキストが実際には他の場所で必要とされる可能性がありますが、実際にはそうではありませんが、これはJavascriptにシリアル化されているため、とにかく消費され、とにかく意味がありません。
仕様書としてDTOのケースを作成できると思いますが、それらを宣言し、プロジェクト内の特別なフォルダーでそれらを検索する保守作業が増えると、より適切な場所と文書化する手段があると主張します。インターフェース;実際、匿名オブジェクトの宣言は十分に文書化されているようです。また、私たちの場合はすべてが内部であり、これはスタンドアロンプロジェクトであったため、これが外部に公開されたAPIであるとは言えないことも付け加えておきます。
なぜDTOを使用する必要があるのかについて私が考慮しなかった他の角度があるかどうか疑問に思っていました。
強く型付けされた言語を使用することは、コンパイル時にエラーをキャッチすることにより、実行時のエラーを防ぐことを目的としています。匿名オブジェクト(または辞書など)を使用すると、この目的が無効になり、実行時にのみ表示されるバグをプログラムにコンパイルできます。
例として、以下のコードを見てください。
public object GetUsers() {
var users = MyRepo.GetAll<User>();
return new {
count: users.Count(),
items: users
}
}
public object GetNewUsers() {
var users = MyRepo.GetNewest<User>();
return new {
Count: users.Count(),
items: users
}
}
最初の応答は小文字のcount
パラメーターを使用し、2番目の応答は大文字のCount
パラメーターを含みます。これらのメソッドが数ブロックのコードで区切られている可能性があるクラスでは、このタイプミスを見逃しがちです。さらに悪いことに、これは喜んでコンパイルして実行します。代わりに型指定されたDTOを使用する場合、この状況は不可能です(実際にはコンパイルさえできません)。
またはこの例はどうですか:
public object GetSystemInstallDate(){
var date = AppHost.GetInstallDate();
return new {
date
};
}
GetInstallDate
オブジェクトの代わりにDateTimeOffset
を返すようにDateTime
メソッドを変更するようにリファクタリングするとどうなりますか?コードは問題なくコンパイルされますが、クライアントへの出力は変更されます。これは弱い例ですが、このようなことが起こり得る他の多くの例があります。数値型、列挙型リファクタリングなど...
匿名の応答が複数の場所で使用されている場合、resharperなどの高度なツールを使用してリファクタリングすることはできません。手動でリファクタリングする必要がありますが、これは決して楽しい作業ではありません。単一の場所で使用する場合、リファクタリングは簡単ですが、最初にクラスを定義するよりも多くの作業が必要です。
匿名型を使用しても、DRYの違反から身を守ることはできないと私は主張します。むしろ、違反する可能性が高くなります。匿名型は1つの場所でのみ使用できます。ただし、その型が表すものが他の場所で必要になる場合は、型全体を再作成する必要があります。
強く型付けされたオブジェクトを使用することで、型を1か所で定義するだけでよく、その型を必要な場所で使用できます。これにより、すべてのプロパティ名、フォーマット値などを繰り返し出力する必要がなくなります。
public object GetTop10Movies() {
return new { lastmodified = DateTime.Now, items }
}
public object GetNewest10Movies() {
// We now have to repeat the previous anonymous object
}
これはガイドラインであり、ルールではありません。それがルールだったとしたら、「未来を無視する」ことになります。これは常に災害のレシピです。同時に、考えられるすべての将来のシナリオをカバーしようとするのは恐ろしい考えであり、過度に抽象的で複雑なコードベースにつながるでしょう。トリックして、前に考えることと目の前の問題を解決することの間の適切なバランスをとります。
この状況でのYAGNIについての別の考え方は、コンパイル時の型安全が必要ですか?
私は、あなたの応答を表すクラスの作成に関わるworkは、匿名オブジェクトの入力に伴う作業よりもほんの少しだけ多いと主張します。複雑さの違いは本質的にはありません(実際、C#の世界では、匿名オブジェクトはおそらくより複雑とるべきルートと見なされます)。
かなり短い時間の投資で、開発者にとって現実的で使用可能なかなりの数の利点を得ることができます。それらは必要ではない可能性があります。これらがないとアプリケーションは機能しませんが、厳密に型指定された言語が必要ない場合は、最初にC#の使用に疑問を呈します。
チームの慣例では、DTOのみを返すことであり、DTOは完全に異なるプロジェクト(ソリューション内の約50のプロジェクト)で管理されていました。この場合、そのパターンを永続化することは過度であり、維持するのが難しいと感じました。
50個のプロジェクトをすべて1つのソリューションにバンドルするという考えを除いて、このパターンを維持することの難しさがどこから来るのかはわかりません。次の2つの状況のいずれかになります。
タイプを表すDTOプロジェクトに新しいファイルを追加し、それをアプリケーションで使用します(プロジェクト間に参照がすでに存在している必要があります)。
DTOアセンブリの名前空間の入力を開始し、必要なタイプを選択します。
仕様書としてDTOのケースを作成できると思いますが、それらを宣言し、プロジェクト内の特別なフォルダーでそれらを検索する保守作業が増えると、より適切な場所と文書化する手段があると主張します。インターフェース;実際、匿名オブジェクトの宣言は十分に文書化されているようです。
コードをドキュメント化する場所は、コードドキュメント自体を配置することほどありません。応答DTOの名前は、それが何であるかを説明する必要があります。あなたの匿名の宣言は、それが何を表しているのかについては何も言っていません。外部のドキュメント(Webサイトやヘルプファイルなど)を検索する必要があるのはさらに厄介です。
2つのボルト、2つのワッシャー、およびナットで構成されるボックスは、それが何のためにあるのかまったく説明していません。ステッカー「モニターウォールマウント」の付いたボックスは、パッケージを見るだけでそれが何であるかを正確に知ることができます。
new { id, model }
// What is this? A vehicle? An economic model? Representation of DNA?
new Vehicle { id = id, model = model }
// Oh! A vehicle!
個人的には、匿名のJsonResultsからDTOに移行しました。私は匿名オブジェクトが大好きでした-それらは素早く、簡単で、実際には何も傷つけません(JsonをサーバーからUIに転送すると、型チェックについてあまり気にする必要がありません...これは単なるシリアル化の手段です)。アジャイルな環境では...私には問題ありません。
しかし、時間の経過とともに、特にUIにデータを返すときに、少し標準化されたフォーマット/変換/処理を必要としないことがよくありました。日付/時刻のフォーマット、測定単位の変換などを行うために、重複したコードが表示されるようになりました。DTOを使用すると、コアインターフェイスといくつかの拡張メソッドを介して、これらの標準化を強制できます。つまり、再利用について話しているのですが、DTO自体についてではありません。
もちろん、それが意味をなさない場合があります。いくつかの整数のタプルを送信する場合は、気にかけてください。ただし、チームが「Jsonアクション用のDTOを作成する」というルールに固執する方が簡単で、長期的にはメリットが得られると思います。費用は安いです。
この場合、匿名型を使用するのが好きです。
これは、サーバーでのデータ変換の最後のステップです。型システムは、操作に値を追加せずにプログラマーの負担を増やすだけです。 Mikeが述べた強い型付けのクライアントに関しては、実際にはより多くの問題が発生することがわかります。サービスプロジェクトとすべてのクライアントプロジェクトの間の結合は、時間とともに増加する傾向があります。
匿名型は名前空間を汚染しません。これの利点を過小評価しないでください。開発者が頻繁に行き来してビジネスが頻繁に変わるプロジェクトでは、開発者は、「良い」名前が採用された場合に作成する必要がある新しいDTOに誤解を招く名前を選択する傾向があり、両方の名前が後者に誤解を与えます。メンテナ。
彼らは 名前を思いつくのは難しい と言っていますが、私はそれが難しいと思います。その愚かな規則は、それらの抽出された宣言に名前を付けることを強制します。それらを抽出する唯一の理由がその規則に従うことである場合、それらに名前を付けることは非常に困難です。言語がとにかく宣言を使用せざるを得なかったとしても、その規則では、宣言を使用法から離れた場所に移動する必要があるため、名前を選択する際に使用法のコンテキストを暗黙的な参照として使用できなくなります。命名作業がはるかに簡単になりました。
それはあなたの質問とどのように関連していましたか?使用法から宣言を抽出するかどうかを考えるとき、大まかな経験則として、宣言するものにどのような名前を付けるかを検討することです。名前が自然に来る場合、それはドメインでそれが何らかの意味を持っていることを意味し、DTOにする必要があります。名前を見つけるのに苦労する必要があり、思いつくことができるのは、使用法に直接関連するもの(<methodname>_result
など)または内部のフィールドの直接の説明だけである場合1(IntAndStringTuple
のように)、プロジェクト内の何かがDTOを使用するように強制される方法でビルドされていない限り、それをDTOにするべきではありません。
あなたの場合、メソッドの名前はGetSomeSuch
で、DTOの名前はSomeSuchDTO2
です。もちろん、これらはプロジェクトで使用されている実際の名前ではありませんが、例として使用します。 SomeSuchDTO2
がGetSomeSuch
にちなんで名付けられている場合、つまり、GetSomeSuch
がない場合、SomeSuchDTO2
の意味を直接理解する方法がない場合は、DTOを作成しないでください。ただし、GetSomeSuch
がSomeSuchDTO2
にちなんで名付けられている場合、つまりSomeSuch
が物である場合、DTOを作成する必要があります。
1「フィールドの直接説明」とは、オブジェクト全体の説明ではなく、オブジェクトを構成するフィールドの説明のことです。例:PersonName
は、フィールドの直接の説明ですnot。そこからフィールドがどうなるかは理解できますが、オブジェクト全体を説明しています。同じクラスのフィールドの直接の説明は、FirstNameAndLastName
のようになります。