DTOに構成と継承を使用する


13

単一ページアプリケーションにREST APIを提供するASP.NET Web APIがあります。DTO / POCOを使用して、このAPIを介してデータを渡します。

問題は、これらのDTOが時間とともに大きくなっていることです。そのため、DTOをリファクタリングしたいと考えています。

DTOを設計する「ベストプラクティス」を探しています。現在、値型フィールドのみで構成される小さなDTOがあります。

public class UserDto
{
    public int Id { get; set; }

    public string Name { get; set; }
}

他のDTOは、構成によってこのUserDtoを使用します。例:

public class TaskDto
{
    public int Id { get; set; }

    public UserDto AssignedTo { get; set; }
}

また、他から継承することによって定義される拡張DTOがいくつかあります。たとえば:

public class TaskDetailDto : TaskDto
{
    // some more fields
}

一部のDTOは複数のエンドポイント/メソッド(GETやPUTなど)に使用されているため、時間とともにフィールドによって段階的に拡張されています。また、継承と構成により、他のDTOも大きくなりました。

私の質問は、継承と構成はグッドプラクティスではないということですか?しかし、それらを再利用しないと、同じコードを複数回書くような気がします。複数のエンドポイント/メソッドにDTOを使用するのは悪い習慣ですか、またはいくつかのニュアンスのみが異なる異なるDTOが必要ですか?


6
私はあなたに何をすべきかを伝えることはできませんが、DTOでの私の経験では、相続と構成はすぐに悪いコーヒーのようにあなたを狩ります。DTOを再利用することはありません。今まで。さらに、同様のDTOがDRY違反であるとは考えていません。同じ表現を返す2つのエンドポイントは、まったく同じDTOを再利用できます。同様の表現を返す2つのエンドポイントが同じDTOを返さないため、それぞれに特定のDTOを作成します。私が選択を余儀なくされた場合、構成は長期的には問題が少ないです。
ライヴ

@Laivこれは質問に対する正しい答えです。わからないあなたはコメントとしてそれを置く理由
TheCatWhisperer

2
@Laiv:代わりに何を使用しますか?私の経験では、これに苦労している人々は単にそれを考え直しています。DTOは単なるデータのコンテナーであり、それだけです。
ロバートハーヴェイ

TheCatWhispererは、私の意見が主に意見に基づいているためです。私はプロジェクトでこの種の問題に対処しようとしています。@RobertHarvey true、なぜ私が物事を実際よりも難しく見る傾向があるのか​​分かりません。私はまだ解決に取り組んでいます。HALはこれらの問題を解決するためのモデルであると確信していましたが、あなたの答えを読んで、DTOの粒度が細かすぎることに気付きました。そこで、まずあなたのアプローチを実践します。変更は、HATEOASに完全に移行するよりも劇的ではありません。
ライヴ

あなたを助けるかもしれない良い議論のためにこれを試してみてください: stackoverflow.com/questions/6297322/...
ジョニー・

回答:


9

ベストプラクティスとして、DTOをできるだけ簡潔にしてみてください。あなたが返す必要があるものだけを返します。必要なものだけを使用してください。それがいくつかの余分なDTOを意味するのであれば、そのようにしてください。

この例では、タスクにユーザーが含まれています。おそらく、そこに完全なユーザーオブジェクトは必要ありません。おそらく、タスクを割り当てられたユーザーの名前だけです。残りのユーザープロパティは必要ありません。

別のユーザーにタスクを再割り当てするとします。再割り当ての投稿中に、完全なユーザーオブジェクトと完全なタスクオブジェクトを渡すように誘惑される場合があります。しかし、本当に必要なのはタスクIDとユーザーIDだけです。これらは、タスクの再割り当てに必要な2つの情報のみであるため、DTOをそのようにモデル化します。これは通常、リーンDTOモデルを目指して努力している場合、すべての休憩呼び出しに個別のDTOがあることを意味します。

また、継承/構成が必要になる場合があります。仕事があるとしましょう。ジョブには複数のタスクがあります。その場合、ジョブを取得すると、そのジョブのタスクのリストも返される場合があります。ですから、作曲に対するルールはありません。それはモデル化されているものに依存します。


できるだけ簡潔にするということは、DTOの細部が多少異なる場合にDTOを再作成する必要があることも意味します。
役員

1
@officer-一般に、はい。
ジョンレイナー

2

システムが厳密にCRUD操作に基づいていない限り、DTOは細かすぎます。ビジネスプロセスまたはアーティファクトを具体化するエンドポイントを作成してみてください。このアプローチは、ビジネスロジック層、およびエリックエヴァンスの「ドメイン駆動型設計」にうまく対応しています。

たとえば、請求書のデータを画面またはフォームに表示してエンドユーザーに表示できるようにするエンドポイントがあるとします。CRUDモデルでは、名前、請求先住所、配送先住所、品目などの必要な情報を収集するために、エンドポイントへの複数の呼び出しが必要になります。ビジネストランザクションコンテキストでは、単一のエンドポイントからの単一のDTOがこの情報をすべて一度に返すことができます。


ロバート、ここで集約ルートを意味しますか?
ジョニー

現在、DTOはフォームのデータ全体を提供するように設計されています。たとえば、todoリストを表示する場合、TodoListDTOにはTaskDTOのリストがあります。ただし、TaskDTOを再利用しているため、それぞれにUserDTOもあります。そのため、問題はバックエンドでクエリされ、ネットワーク経由で送信される大量のデータです。
役員

すべてのデータが必要ですか?
ロバートハーベイ

1

DTOのよ​​うな「柔軟な」または抽象的なもののベストプラクティスを確立するのは困難です。基本的に、DTOはデータ転送のオブジェクトにすぎませんが、転送先または転送の理由に応じて、異なる「ベストプラクティス」を適用することができます。

Martin Fowlerによる「エンタープライズアプリケーションアーキテクチャのパターン」を読むことをお勧めします。DTOが非常に詳細なセクションを取得するパターン専用の章全体があります。

もともとは、ロジックのさまざまな部分から大量のデータが必要になる可能性が高い、高価なリモートコールで使用するように「設計」されていました。DTOは、1回の呼び出しでデータの転送を行います。

著者によると、DTOはローカル環境での使用を意図したものではありませんでしたが、一部の人々はDTOの使用を発見しました。通常、それらは異なるPOCOからGUI、API、または異なるレイヤーの単一のエンティティに情報を収集するために使用されます。

現在、継承では、コードの再利用は、主な目的ではなく、継承の副作用に似ています。一方、コンポジションは、コードの再利用を主な目的として実装されます。

一部の人々は、構成と継承を一緒に使用することを推奨します。両方の長所を使用し、それらの弱点を軽減しようとします。以下は、新しいDTO、またはそのことに関する新しいクラス/オブジェクトを選択または作成するときの私の精神的プロセスの一部です。

  • 同じレイヤーまたは同じコンテキスト内のDTOで継承を使用します。DTOはPOCOから継承することはなく、BLL DTOはDAL DTOから継承することはありません。
  • DTOからフィールドを非表示にしようとしていることに気付いた場合は、リファクタリングし、代わりにコンポジションを使用します。
  • 基本DTOの異なるフィールドがほとんど必要ない場合は、それらをユニバーサルDTOに入れます。ユニバーサルDTOは内部でのみ使用されます。
  • ベースPOCO / DTOがロジックに使用されることはほとんどないため、ベースはその子のニーズにのみ応答します。ベースを使用する必要がある場合、その子が決して使用しない新しいフィールドを追加することは避けます。

それらのいくつかは「ベスト」プラクティスではないかもしれません。それらは私が取り組んでいるプロジェクトでは非常にうまく機能しますが、すべてに合うサイズはないことを覚えておく必要があります。ユニバーサルDTOの場合、注意が必要です。私のメソッドシグネチャは次のようになります。

public void DoSomething(BaseDTO base) {
    //Some code 
}

メソッドのいずれかが独自のDTOを必要とする場合、継承を行います。通常、変更する必要があるのはパラメーターのみですが、特定のケースではさらに深く掘り下げる必要がある場合があります。

あなたのコメントから、ネストされたDTOを使用していることがわかります。ネストされたDTOが他のDTOのリストのみで構成されている場合、リストのラップを解除するのが最善だと思います。

表示または操作する必要があるデータの量によっては、データを制限する新しいDTOを作成することをお勧めします。たとえば、UserDTOに多数のフィールドがあり、1または2のみが必要な場合は、それらのフィールドのみでDTOを使用することをお勧めします。DTOのレイヤー、コンテキスト、使用法、ユーティリティを定義することは、DTOを設計する際に非常に役立ちます。


回答に3つ以上のリンクを追加することはできませんでした。ローカルDTOについての詳細情報を次に示します。私はその非常に古い情報を知っていますが、その一部はまだ関連していると思います。
IvanGrasp

1

DTOでコンポジションを使用することは、完全に素晴らしいプラクティスです。

DTOの具体的なタイプ間の継承は、悪い習慣です。

1つには、C#のような言語では、自動実装プロパティのメンテナンスオーバーヘッドが非常に少ないため、それらを複製することは(実際には複製を嫌います)、それほど有害ではありません。

DTO間で具体的な継承を使用しない理由の1つは、特定のツールがそれらを間違った型にうまくマッピングすることです。

たとえば、class name -> table name推論を実行するDapperなどのデータベースユーティリティ(お勧めしませんが、一般的です)を使用すると、派生型を階層内のどこかで具体的な基本型として簡単に保存でき、それによってデータが失われるか、さらに悪化する可能性があります。

DTO間で継承を使用しないより深い理由は、明白な「is a」関係を持たないタイプ間で実装を共有するために使用すべきではないということです。私の考えでTaskDetailは、のサブタイプのようには聞こえませんTask。それは同じように簡単にのプロパティである可能性がありTask、さらに悪いことに、それはのスーパータイプである可能性がありTaskます。

さて、気になるかもしれないことの1つは、関連するさまざまなDTOのプロパティの名前タイプの一貫性を維持することです。

結果として、具象型の継承はそのような一貫性を確保するのに役立ちますが、そのような一貫性を維持するためにインターフェース(またはC ++の純粋仮想ベースクラス)を使用する方がはるかに優れています。

次のDTOを検討してください

interface IIdentity
{
    int Id { get; set; }
}

interface INamed
{
    string Name { get; set; }
}

public class UserDto: IIdentity, INamed
{
    public int Id { get; set; }

    public string Name { get; set; }

    // User specific properties
}

public class TaskDto: IIdentity
{
    public int Id { get; set; }

    // Task specific properties
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.