Best Practices für die Zuordnung von DTO zu Domänenobjekten?

81

Ich habe viele Fragen im Zusammenhang mit der Zuordnung von DTOs zu Domänenobjekten gesehen , aber ich hatte nicht das Gefühl, dass sie meine Frage beantwortet haben. Ich habe schon viele Methoden angewendet und habe meine eigene Meinung, aber ich suche etwas Konkreteres.

Die Situation:

Wir haben viele Domain-Objekte. Wir verwenden ein CSLA-Modell, sodass unsere Domänenobjekte sehr komplex sein können und ihren eigenen Datenzugriff enthalten. Sie möchten diese nicht auf dem Draht weitergeben. Wir werden einige neue Dienste schreiben, die Daten in einer Reihe von Formaten (.Net, JSON usw.) zurückgeben. Aus diesem (und anderen Gründen) erstellen wir auch ein schlankes Datenübertragungsobjekt, das auf dem Draht herumgereicht werden soll.

Meine Frage ist: Wie sollen das DTO- und das Domain-Objekt verbunden werden?

Meine erste Reaktion ist die Verwendung einer Fowler-DTO-Musterlösung . Ich habe das schon oft gesehen und es fühlt sich für mich richtig an. Das Domänenobjekt enthält keinen Verweis auf das DTO. Eine externe Entität (ein "Mapper" oder "Assembler") wird aufgerufen, um ein DTO aus einem Domänenobjekt zu erstellen. Normalerweise befindet sich auf der Domänenobjektseite ein ORM . Der Nachteil dabei ist, dass der "Mapper" für jede reale Situation extrem komplex wird und sehr zerbrechlich sein kann.

Eine andere Idee ist, dass das Domänenobjekt das DTO "enthält", da es nur ein schlankes Datenobjekt ist. Die Domänenobjekteigenschaften verweisen intern auf die DTO-Eigenschaften und können das DTO nur zurückgeben, wenn Sie dazu aufgefordert werden. Ich kann keine Probleme damit sehen, aber es fühlt sich falsch an. Ich habe einige Artikel gesehen, in denen Leute, die NHibernate verwenden, diese Methode zu verwenden schienen .

Gibt es andere Möglichkeiten? Lohnt sich eine der oben genannten Möglichkeiten? Wenn ja oder wenn nicht, warum?

Brian Ellis
quelle
4
Der Automapper sieht interessant aus. Ich habe schon viel Code gesehen, den er ersetzt hätte. Mein Hauptproblem dort ist, dass ich es vorziehen würde, selbst die Kontrolle darüber zu haben, wenn ich aus irgendeinem Grund mit einer Menge Mapping-Code nicht weiterkomme.
Brian Ellis
2
Wenn wir von DTOs zu Domänenobjekten wechseln , erfolgt diese Zuordnung zu 100% manuell. Es ist viel schwieriger, ein Problem zu lösen, da wir versuchen, unsere Domänenobjekte betriebsbasiert zu halten, anstatt nur Datencontainer. Möchten Sie zu einem DTO, das ist ein einfaches Problem zu lösen.
Jimmy Bogard
Eine weitere Option ist die Beta-Version von ServiceToolkit.NET, die wir während unseres letzten Projekts gestartet haben. Vielleicht kann es Ihnen helfen: http://servicetoolkit.codeplex.com/
Ich würde zustimmen, dass es falsch ist, dass das Domänenobjekt keine Kenntnis vom dto-Objekt haben sollte. Während sie in diesem Fall verwandt sein können, ist ihr Zweck völlig getrennt (dtos werden im Allgemeinen für den Zweck erstellt) und Sie würden eine unnötige Abhängigkeit erstellen.
Sinaesthetic

Antworten:

40

Ein Vorteil eines Mappers zwischen Ihrer Domain und Ihrem DTO ist nicht so offensichtlich, wenn Sie nur eine einzelne Zuordnung unterstützen. Wenn jedoch die Anzahl der Mappings zunimmt, hilft die Isolierung dieses Codes von der Domain, die Domain einfacher und schlanker zu halten. Sie werden Ihre Domain nicht mit viel zusätzlichem Gewicht überladen.

Persönlich versuche ich, die Zuordnung von meinen Domänenentitäten fernzuhalten und die Verantwortung in die sogenannte "Manager / Service-Schicht" zu legen. Dies ist eine Schicht, die sich zwischen der Anwendung und dem / den Respository (s) befindet und Geschäftslogik wie die Workflow-Koordination bereitstellt (Wenn Sie A ändern, müssen Sie möglicherweise auch B ändern, damit Service A mit Service B zusammenarbeitet).

Wenn ich viele mögliche Endformate hätte, könnte ich einen steckbaren Formatierer erstellen, der das Besuchermuster verwenden könnte, um beispielsweise meine Entitäten zu transformieren, aber ich habe noch keinen Bedarf für etwas so Komplexes gefunden.

JoshBerke
quelle
"(Wenn Sie A ändern, müssen Sie möglicherweise auch B ändern, damit Service A mit Service B funktioniert.)" - Fällt dies nicht unter die Geschäftslogik? Ich denke, dieser Teil sollte eher an den Controller als an den Service gehen?
Ayyappa
24

Sie könnten einen Automapper wie den von Jimmy Bogard verwenden, der keine Verbindung zwischen den Objekten hat und auf der Einhaltung von Namenskonventionen beruht.

Garry Shutler
quelle
9
Automapper kann zu versehentlich freigelegten Eigenschaften führen -> Sicherheitslücke. Es wäre besser, explizit zu sagen, was als DTO angezeigt werden soll.
Deamon
4
@deamon: berechtigte Bedenken, aber auch die Fehler (und potenziellen Sicherheitslücken aufgrund menschlicher Aufsicht), die beim Schreiben des gesamten klebrigen Mapping-Codes entstehen können. Ich werde die automagische Straße gehen und die 5% mithilfe der integrierten benutzerdefinierten Zuordnungsfunktion bewältigen.
Merritt
@deamon - können Sie nicht einfach die bedingte Zuordnung für die Eigenschaften durchführen, die Sie nicht verfügbar machen sollten? Denken Sie, dass AutoMapper dieses Szenario handhabt?
Richard B
Wenn Sie AutoMapper verwenden, ist es meiner Meinung nach äußerst wichtig, dass alle Komponententests vorhanden sind, um zu testen, ob die Zuordnung korrekt durchgeführt wurde.
L-Four
7

Wir verwenden T4-Vorlagen, um die Mapping-Klassen zu erstellen.

Pro's - von Menschen lesbarer Code, der zur Kompilierungszeit verfügbar ist und schneller als ein Laufzeit-Mapper ist. 100% Kontrolle über den Code (kann Teilmethoden / Vorlagenmuster verwenden, um die Funktionalität auf Ad-hoc-Basis zu erweitern)

Con's - ohne bestimmte Eigenschaften, Sammlungen von Domänenobjekten usw., Erlernen der T4-Syntax.

SturmUndDrang
quelle
6

Wenn Sie die Zuordnungslogik in Ihrer Entität behalten, ist Ihrem Domänenobjekt jetzt ein "Implementierungsdetail" bekannt, über das es nichts wissen muss. Im Allgemeinen ist ein DTO Ihr Gateway zur Außenwelt (entweder von einer eingehenden Anfrage oder über einen Lesevorgang von einem externen Dienst / einer externen Datenbank). Da die Entität Teil Ihrer Geschäftslogik ist, ist es wahrscheinlich am besten, diese Details außerhalb der Entität zu halten.

Das Mapping woanders zu behalten wäre die einzige Alternative - aber wohin sollte es gehen? Ich habe versucht, Mapping-Objekte / -Dienste einzuführen, aber nachdem alles gesagt und getan war, schien es wie Überentwicklung (und war es wahrscheinlich). Ich hatte einige Erfolge mit Automapper und dergleichen für kleinere Projekte, aber Tools wie Automapper haben ihre eigenen Fallstricke. Ich hatte einige ziemlich schwer zu findende Probleme im Zusammenhang mit Zuordnungen, da die Zuordnungen von Automapper implizit und vollständig vom Rest Ihres Codes entkoppelt sind (nicht wie "Trennung von Bedenken", sondern eher wie "Wo lebt die gottverlassene Zuordnung?") kann manchmal schwer zu finden sein. Um nicht zu sagen, dass Automapper seine Verwendung nicht hat, weil es tut. Ich denke nur, Mapping sollte so offensichtlich und transparent wie möglich sein, um Probleme zu vermeiden.

Anstatt eine Mapping-Service-Schicht zu erstellen, hatte ich viel Erfolg damit, meine Mappings in meinen DTOs zu behalten. Da DTOs immer an der Grenze der Anwendung sitzen, können sie auf das Geschäftsobjekt aufmerksam gemacht werden und herausfinden, wie sie von / zu ihnen zugeordnet werden können. Selbst wenn die Anzahl der Zuordnungen auf einen angemessenen Wert skaliert wird, funktioniert dies sauber. Alle Zuordnungen befinden sich an einem Ort, und Sie müssen nicht eine Reihe von Zuordnungsdiensten in Ihrer Datenschicht, Antikorruptionsschicht oder Präsentationsschicht verwalten. Stattdessen ist das Mapping nur ein Implementierungsdetail, das an das an der Anforderung / Antwort beteiligte DTO delegiert ist. Da Serialisierer im Allgemeinen nur Eigenschaften und Felder serialisieren, wenn Sie sie über das Kabel senden, sollten Sie auf keine Probleme stoßen. Persönlich fand ich dies die sauberste Option und ich kann meiner Erfahrung nach sagen:

Kyle Goode
quelle
3

Wie sehen Sie die Implementierung eines Konstruktors in der DTO-Klasse, der ein Domänenobjekt als Parameter verwendet?

Sag ... so etwas

class DTO {

     // attributes 

     public DTO (DomainObject domainObject) {
          this.prop = domainObject.getProp();
     }

     // methods
}
Sieger
quelle
9
Bitte, tu das niemals. Sie möchten nicht, dass Ihre DTO-Schicht Ihre Domänenschicht kennt oder von dieser abhängig ist. Der Vorteil der Zuordnung besteht darin, dass die unteren Ebenen durch Ändern der Zuordnung leicht ausgetauscht werden können oder dass Änderungen in der unteren Ebene durch Ändern der Zuordnung gesteuert werden können. Angenommen, dtoA wird heute domainObjectA zugeordnet, morgen ist es jedoch erforderlich, dass es domainObjectB zugeordnet wird. In Ihrem Fall müssen Sie das DTO-Objekt ändern, was ein großes Nein-Nein ist. Sie haben viele Vorteile des Mappers verloren.
Frederik Prijck
2
Zunächst einmal danke! : D. @FrederikPrijck Durch Einfügen einer Ebene zwischen der DTOund der DomainObject, bemühen wir uns grundsätzlich, dass dieses Problem des DTO vom Domänenobjekt abhängt, sodass alle "Bauarbeiten" in einer mittleren Ebene (Klasse) ausgeführt werden mapper, die von beiden DTOs abhängig ist und DomainObjects. Das ist also der beste oder allgemein empfohlene Ansatz für diese Angelegenheit? Ich möchte nur sicherstellen, dass der Punkt verstanden wurde.
Victor
4
Ja, die Ebene heißt "Assembler". Durch die Verwendung einer dritten Ebene zum Definieren der Zuordnungen können Sie die Assembler-Ebene problemlos durch eine andere Implementierung ersetzen (z. B. Automapper entfernen und manuelle Zuordnungen verwenden). Dies ist immer die bessere Wahl. Der beste Weg, dies zu verstehen, besteht darin, sich zu überlegen, wo ich Ihnen Objekt A und jemand anderes Objekt B geben würde. Sie haben keinen Zugriff auf jedes dieser Objekte (nur DLL), sodass die Zuordnung nur durch Erstellen eines dritten Objekts erfolgen kann Schicht. Aber selbst wenn Sie auf eines der Objekte zugreifen können, sollten Zuordnungen immer außerhalb vorgenommen werden, da sie nicht miteinander verbunden sind.
Frederik Prijck
1
Aber diese Antwort ist in der Tat "mehr als nützlich" mit den Kommentaren und Korrekturen, sie bringt jedem Leser Anerkennung und Tipps über das Problem. Sie trägt wirklich zum Lernen bei, ich verstehe nicht, warum ich nicht abstimmen soll. Sie hilft mir .. aber ich möchte keine Diskussion darüber beginnen .. bis zu dir. Trotzdem danke für die Antwort.
Victor
3
Eigentlich gefällt mir dieser Ansatz, derzeit verwende ich den Konstruktor, um die Entität dem DTO zuzuordnen, und verwende eine Mapper-Klasse, um die Eingabe dto der Entität zuzuordnen.
Traum83619
1

Eine weitere mögliche Lösung: http://glue.codeplex.com .

Eigenschaften:

  • Bidirektionale Zuordnung
  • Automatische Zuordnung
  • Zuordnung zwischen verschiedenen Typen
  • Verschachtelte Zuordnung und Reduzierung
  • Listen und Arrays
  • Überprüfung der Beziehungen
  • Mapping testen
  • Eigenschaften, Felder und Methoden
Saly
quelle
0

Ich kann ein Tool vorschlagen, das ich erstellt habe und das Open Source ist und das bei CodePlex gehostet wird: EntitiesToDTOs .

Die Zuordnung von DTO zu Entity und umgekehrt wird durch Erweiterungsmethoden implementiert, die die Assembler-Seite jedes Endes bilden.

Sie enden mit Code wie:

Foo entity = new Foo();
FooDTO dto = entity.ToDTO();
entity = dto.ToEntity();

List<Foo> entityList = new List<Foo>();
List<FooDTO> dtoList = entityList.ToDTOs();
entityList = dtoList.ToEntities();
kzfabi
quelle
Dies ist architektonisch falsch, da Sie DTO- und Domänenentitäten aufeinander aufmerksam machen.
Raffaeu
5
@ Raffaeu Ich glaube nicht, da ToDTO / ToDTOs / ToEntity / ToEntities-Methoden als Erweiterungsmethoden definiert sind, die die Assembler darstellen. Die Logik zum Konvertieren einer Entität in ein DTO und umgekehrt liegt in den Erweiterungsmethoden (Assemblers), nicht in der Entität / DTO.
Kzfabi
2
Wenn Sie über "Assembler" sprechen, implementieren Sie diese korrekt. Machen Sie sie modular, machen Sie sie einfach austauschbar, verwenden Sie Abhängigkeitsinjektion. Es ist nicht erforderlich, dass das Domänenmodell selbst eine Konvertierung in DTO kennt. Angenommen, ich habe 1 Domänenobjekt, aber 50 verschiedene Anwendungen, die dieselbe Domäne verwenden und jeweils ein eigenes DTO haben. Sie werden keine 50 Erweiterungen erstellen. Stattdessen erstellen Sie für jede Anwendung einen Anwendungsdienst, wobei die erforderlichen Assembler als Abhängigkeit in den Dienst eingefügt werden.
Frederik Prijck
0

Warum können wir das nicht so machen?

class UserDTO {
}

class AdminDTO {
}

class DomainObject {

 // attributes
 public DomainObject(DTO dto) {
      this.dto = dto;
 }     

 // methods
 public function isActive() {
      return (this.dto.getStatus() == 'ACTIVE')
 }

 public function isModeratorAdmin() {
      return (this.dto.getAdminRole() == 'moderator')
 }

}


userdto = new UserDTO();
userdto.setStatus('ACTIVE');

obj = new DomainObject(userdto)
if(obj.isActive()) {
   //print active
}

admindto = new AdminDTO();
admindto.setAdminRole('moderator');

obj = new DomainObject(admindto)
if(obj.isModeratorAdmin()) {
   //print some thing
}

@FrederikPrijck (oder) jemand: Bitte vorschlagen. Im obigen Beispiel hängt DomainObject von DTO ab. Auf diese Weise kann ich den Code für die Zuordnung des Domänenobjekts dto <--> vermeiden.

oder DomainObject-Klasse kann die DTO-Klasse erweitern?

user3767551
quelle
0

Eine andere Option wäre die Verwendung von ModelProjector . Es unterstützt alle möglichen Szenarien und ist sehr einfach bei minimalem Platzbedarf zu verwenden.

user10269
quelle
0

Wir können dafür Factory-, Memento- und Builder-Muster verwenden. Werkseitig verbergen Sie die Details zum Erstellen einer Instanz des Domänenmodells aus DTO. Memento kümmert sich um die Serialisierung / Deserialisierung des Domänenmodells zu / von DTO und kann sogar auf private Mitglieder zugreifen. Der Builder ermöglicht die Zuordnung von DTO zu Domäne mit fließender Schnittstelle.

Irwansyah
quelle