単一ページアプリケーションに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があるはずですか?
ベストプラクティスとして、DTOをできるだけ簡潔にしてください。あなたが返す必要があるものだけを返します。必要なものだけを使用してください。それがいくつかの追加のDTOを意味する場合は、そうです。
この例では、タスクにユーザーが含まれています。おそらく、完全なユーザーオブジェクトは必要ありません。タスクが割り当てられているユーザーの名前だけかもしれません。残りのユーザープロパティは必要ありません。
タスクを別のユーザーに再割り当てしたいとします。再割り当ての投稿中に、完全なユーザーオブジェクトと完全なタスクオブジェクトを渡そうとする場合があります。しかし、本当に必要なのはタスクIDとユーザーIDだけです。これらは、タスクを再割り当てするために必要な2つの情報だけなので、DTOをそのようにモデル化します。これは通常、無駄のないDTOモデルを目指している場合、すべての休憩呼び出しに対して個別のDTOがあることを意味します。
また、継承/構成が必要になる場合もあります。仕事があるとしましょう。ジョブには複数のタスクがあります。その場合、ジョブを取得すると、そのジョブのタスクのリストも返される場合があります。だから、作曲に対するルールはありません。それはモデル化されているものに依存します。
システムが厳密にCRUD操作に基づいている場合を除いて、DTOは細かすぎます。ビジネスプロセスまたはアーティファクトを具体化するエンドポイントを作成してみてください。このアプローチは、ビジネスロジックレイヤーとEric Evansの「ドメイン駆動設計」にうまく対応しています。
たとえば、エンドユーザーが画面またはフォームに表示できるように、請求書のデータを返すエンドポイントがあるとします。 CRUDモデルでは、名前、請求先住所、配送先住所、ラインアイテムなどの必要な情報を集めるために、エンドポイントへのいくつかの呼び出しが必要になります。ビジネストランザクションのコンテキストでは、単一のエンドポイントからの単一のDTOがこの情報のすべてを一度に返すことができます。
DTOでコンポジションを使用することは、完全に良い習慣です。
DTOの具体的なタイプ間の継承は悪い習慣です。
1つには、C#のような言語では、自動実装されたプロパティには保守性のオーバーヘッドがほとんどないため、それらを複製しても(実際には複製を嫌います)、それほど害はありません。
DTO間で具体的な継承を使用する1つの理由notは、特定のツールがそれらを間違った型にうまくマップするためです。
たとえば、class name -> table name
推論を実行するDapper(私はお勧めしませんが、人気があります)などのデータベースユーティリティを使用すると、派生型を階層のどこかに具象ベース型として簡単に保存できます。これにより、データが失われるか、さらに悪化します。
DTO間で継承を使用するnotを使用するより深い理由は、明白な「is a」関係を持たない型間で実装を共有するために使用しないでください。私の考えでは、TaskDetail
はTask
のサブタイプのようには聞こえません。これは、Task
のプロパティと同じように簡単にできます。さらに悪いことに、Task
のスーパータイプになることもあります。
ここで、気になる可能性があるのは、関連するさまざまなDTOのプロパティのnamesとtypesの間の一貫性を維持することです。
具象型の継承は結果としてそのような一貫性を保証するのに役立ちますが、そのような一貫性を維持するには、インターフェイス(または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
}
DTOのように「柔軟」または抽象的なもののベストプラクティスを確立するのは困難です。基本的に、DTOはデータ転送のオブジェクトにすぎませんが、転送先や転送の理由によっては、異なる「ベストプラクティス」を適用する必要がある場合があります。
Patterns of Enterprise Application Architecture by Martin Fowler を読むことをお勧めします。パターンに特化した章全体があり、DTOは本当に詳細なセクションを取得します。
もともとは、ロジックのさまざまな部分から大量のデータが必要になる可能性が高い高価なリモート呼び出しで使用するように「設計」されていました。 DTOは、1回の呼び出しでデータの転送を行います。
著者によると、DTOはローカル環境での使用を目的としていませんでしたが、一部の人々はDTOの使用を発見しました。通常、これらは、異なるPOCOからGUI、API、または異なるレイヤーの単一のエンティティに情報を収集するために使用されます。
現在、継承により、コードの再利用は、その主な目的ではなく、継承の副作用のようなものです。一方、合成は、コードの再利用を主な目的として実装されます。
一部の人々 構成と継承を組み合わせて使用し、両方の長所を使用し、弱点を緩和しようとすることを推奨します。以下は、新しいDTO、またはそのための新しいクラス/オブジェクトを選択または作成するときの私の精神的なプロセスの一部です。
それらのうちのいくつかは「ベスト」プラクティスではないかもしれません。それらは私が取り組んできたプロジェクトには非常にうまく機能しますが、サイズがすべてに当てはまるわけではないことを覚えておく必要があります。ユニバーサルDTOの場合は注意が必要です。私のメソッドシグネチャは次のようになります。
public void DoSomething(BaseDTO base) {
//Some code
}
メソッドのいずれかが独自のDTOを必要とする場合、継承を行います。通常、必要な唯一の変更はパラメーターですが、特定のケースではさらに深く掘り下げる必要があります。
あなたのコメントから、ネストされたDTOを使用していることがわかります。ネストされたDTOが他のDTOのリストのみで構成されている場合は、リストを展開することが最善の策だと思います。
表示または操作する必要のあるデータの量によっては、データを制限する新しいDTOを作成することをお勧めします。たとえば、UserDTOに多くのフィールドがあり、1つまたは2つだけが必要な場合は、それらのフィールドのみを含むDTOを使用することをお勧めします。 DTOのレイヤー、コンテキスト、使用法、およびユーティリティを定義すると、DTOを設計するときに非常に役立ちます。