Wir haben eine ASP.NET-Web-API, die eine REST-API für unsere Einzelseitenanwendung bereitstellt. Wir verwenden DTOs / POCOs, um Daten über diese API weiterzuleiten.
Das Problem ist nun, dass diese DTOs im Laufe der Zeit immer größer werden. Jetzt wollen wir die DTOs umgestalten.
Ich suche nach "Best Practices" zum Entwerfen eines DTO: Derzeit gibt es kleine DTOs, die nur aus Feldern vom Typ "Wert" bestehen, z.
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Andere DTOs verwenden diese UserDto nach Zusammensetzung, z.
public class TaskDto
{
public int Id { get; set; }
public UserDto AssignedTo { get; set; }
}
Es gibt auch einige erweiterte DTOs, die durch Erben von anderen definiert werden, z.
public class TaskDetailDto : TaskDto
{
// some more fields
}
Da einige DTOs für mehrere Endpunkte / Methoden (z. B. GET und PUT) verwendet wurden, wurden sie im Laufe der Zeit schrittweise um einige Felder erweitert. Und aufgrund der Vererbung und Zusammensetzung wurden auch andere DTOs größer.
Meine Frage ist nun, ob Vererbung und Zusammensetzung keine guten Praktiken sind. Aber wenn wir sie nicht wiederverwenden, fühlt es sich an, als würde man denselben Code mehrmals schreiben. Ist es eine schlechte Praxis, ein DTO für mehrere Endpunkte / Methoden zu verwenden, oder sollte es unterschiedliche DTOs geben, die sich nur in einigen Nuancen unterscheiden?
Antworten:
Versuchen Sie als Best Practice, Ihre DTOs so präzise wie möglich zu gestalten. Geben Sie nur das zurück, was Sie für die Rücksendung benötigen. Verwenden Sie nur das, was Sie benötigen. Wenn das ein paar zusätzliche DTOs bedeutet, dann sei es so.
In Ihrem Beispiel enthält eine Aufgabe einen Benutzer. Wahrscheinlich wird dort kein vollständiges Benutzerobjekt benötigt, sondern nur der Name des Benutzers, dem die Aufgabe zugewiesen wurde. Wir brauchen den Rest der Benutzereigenschaften nicht.
Angenommen, Sie möchten eine Aufgabe einem anderen Benutzer neu zuweisen. Möglicherweise besteht die Versuchung, während des Postens der Neuzuweisung ein vollständiges Benutzerobjekt und ein vollständiges Aufgabenobjekt zu übergeben. Was jedoch wirklich benötigt wird, ist nur die Task-ID und die Benutzer-ID. Dies sind die einzigen beiden Informationen, die zum erneuten Zuweisen einer Aufgabe erforderlich sind. Modellieren Sie das DTO als solches. Dies bedeutet normalerweise, dass für jeden Rest-Aufruf ein separates DTO vorhanden ist, wenn Sie ein Lean-DTO-Modell anstreben.
Manchmal braucht man auch Vererbung / Komposition. Nehmen wir an, man hat einen Job. Ein Job hat mehrere Aufgaben. In diesem Fall wird beim Abrufen eines Jobs möglicherweise auch die Liste der Aufgaben für den Job zurückgegeben. Es gibt also keine Regel gegen Komposition. Es kommt darauf an, was modelliert wird.
quelle
Sofern Ihr System nicht ausschließlich auf CRUD-Operationen basiert, sind Ihre DTOs zu detailliert. Versuchen Sie, Endpunkte zu erstellen, die Geschäftsprozesse oder Artefakte verkörpern. Dieser Ansatz lässt sich gut auf eine Geschäftslogikebene und auf Eric Evans '"domänenorientiertes Design" übertragen.
Angenommen, Sie haben einen Endpunkt, der Daten für eine Rechnung zurückgibt, damit diese dem Endbenutzer auf einem Bildschirm oder in einem Formular angezeigt werden können. In einem CRUD-Modell benötigen Sie mehrere Anrufe an Ihre Endpunkte, um die erforderlichen Informationen zusammenzustellen: Name, Rechnungsadresse, Versandadresse, Werbebuchungen. In einem Geschäftstransaktionskontext kann ein einzelner DTO von einem einzelnen Endpunkt alle diese Informationen auf einmal zurückgeben.
quelle
Es ist schwierig, Best Practices für etwas so "Flexibles" oder Abstraktes wie ein DTO zu etablieren. Im Wesentlichen sind DTOs nur Objekte für die Datenübertragung. Abhängig vom Ziel oder dem Grund für die Übertragung möchten Sie möglicherweise unterschiedliche "Best Practices" anwenden.
Ich empfehle, Patterns of Enterprise Application Architecture von Martin Fowler zu lesen . Es gibt ein ganzes Kapitel über Muster, in dem DTOs einen wirklich detaillierten Abschnitt erhalten.
Ursprünglich waren sie für die Verwendung in teuren Fernaufrufen konzipiert, bei denen Sie wahrscheinlich viele Daten aus verschiedenen Teilen Ihrer Logik benötigen würden. DTOs würden die Datenübertragung in einem einzigen Anruf durchführen.
Laut dem Autor waren DTOs nicht für die Verwendung in lokalen Umgebungen vorgesehen, aber einige Leute fanden eine Verwendung für sie. Normalerweise werden sie verwendet, um Informationen von verschiedenen POCOs in einer einzigen Entität für GUIs, APIs oder verschiedene Ebenen zu sammeln.
Mit der Vererbung ähnelt die Wiederverwendung von Code eher einer Nebenwirkung der Vererbung als ihrem Hauptziel. Komposition hingegen wird mit der Wiederverwendung von Code als Hauptziel implementiert.
Einige Leute empfehlen die Verwendung von Komposition und Vererbung zusammen, wobei sie die Stärken beider nutzen und versuchen, ihre Schwächen zu mildern. Folgendes ist Teil meines mentalen Prozesses, wenn ich neue DTOs oder neue Klassen / Objekte auswähle oder erstelle:
Einige von ihnen sind vielleicht nicht die "besten" Praktiken, sie funktionieren ziemlich gut für die Projekte, an denen ich gearbeitet habe, aber Sie müssen sich daran erinnern, dass keine Größe für alle passt. Beim universellen DTO sollten Sie vorsichtig sein, meine Methodensignaturen sehen folgendermaßen aus:
Wenn eine der Methoden jemals ein eigenes DTO benötigt, übernehme ich die Vererbung und normalerweise muss ich nur den Parameter ändern, obwohl ich in bestimmten Fällen manchmal tiefer gehen muss.
Aus Ihren Kommentaren gehe hervor, dass Sie verschachtelte DTOs verwenden. Wenn Ihre verschachtelten DTOs nur aus einer Liste anderer DTOs bestehen, ist es meines Erachtens das Beste, die Liste auszupacken.
Abhängig von der Datenmenge, die Sie anzeigen oder bearbeiten müssen, ist es möglicherweise eine gute Idee, neue DTOs zu erstellen, die die Daten begrenzen. Wenn Ihr UserDTO beispielsweise viele Felder hat und Sie nur 1 oder 2 benötigen, ist es möglicherweise besser, ein DTO nur mit diesen Feldern zu haben. Das Definieren der Ebene, des Kontexts, der Verwendung und des Nutzens eines DTO wird beim Entwerfen sehr hilfreich sein.
quelle
Die Verwendung von Komposition in DTOs ist eine sehr gute Übung.
Die Vererbung zwischen konkreten Arten von DTOs ist eine schlechte Praxis.
Zum einen hat eine automatisch implementierte Eigenschaft in einer Sprache wie C # einen sehr geringen Wartungsaufwand, so dass das Duplizieren (ich verabscheue wirklich das Duplizieren) nicht so schädlich ist wie es oft ist.
Ein Grund , die konkrete Vererbung unter DTOs nicht zu verwenden, ist, dass bestimmte Tools sie gerne den falschen Typen zuordnen.
Wenn Sie beispielsweise ein Datenbankdienstprogramm wie Dapper verwenden (ich empfehle es nicht, aber es ist sehr beliebt), das
class name -> table name
Inferenzen ausführt , können Sie einen abgeleiteten Typ leicht irgendwo in der Hierarchie als konkreten Basistyp speichern und dadurch Daten verlieren oder schlimmeres .Ein tieferer Grund nicht Gebrauch Erbe unter DTOs ist , dass es nicht zu teilen Implementierungen zwischen Typen verwendet werden soll , dass eine offensichtlich nicht haben „ist eine“ Beziehung. Meiner Meinung nach
TaskDetail
klingt das nicht nach einem Untertyp vonTask
. Es könnte genauso gut eine Eigenschaft vonTask
oder, schlimmer noch, ein Supertyp von seinTask
.Eine Sache, die Sie möglicherweise befürchten, ist die Wahrung der Konsistenz zwischen den Namen und Typen der Eigenschaften verschiedener verwandter DTOs.
Die Vererbung konkreter Typen würde folglich dazu beitragen, eine solche Konsistenz zu gewährleisten. Es ist jedoch viel besser, Schnittstellen (oder reine virtuelle Basisklassen in C ++) zu verwenden, um diese Art von Konsistenz aufrechtzuerhalten.
Betrachten Sie die folgenden DTOs
quelle