CodeGym /Kurse /C# SELF /Problem mit Klassen für Datentransfer (DTO)

Problem mit Klassen für Datentransfer (DTO)

C# SELF
Level 19 , Lektion 0
Verfügbar

1. Einführung in DTO

DTO (Data Transfer Object, „Datenübertragungsobjekt“) – ein Begriff aus der professionellen Programmierung, besonders aus der Welt der verteilten Anwendungen und Services (zum Beispiel, wenn ein Service einen anderen übers Netzwerk aufruft). Im Grunde ist das einfach ein Objekt – meistens eine ganz normale C#-Klasse –, dessen einzige Aufgabe es ist: einen Datensatz zu enthalten und sicher zwischen Programm-Schichten oder zwischen Apps übertragen zu werden.

Stell dir einen Chat zwischen Client und Server vor. Jedes Mal, wenn ein User eine Nachricht schickt, baut der Client ein Objekt mit Text, Zeit und Username, schickt das an den Server, und der entscheidet dann, was damit passiert. Es wäre cool, wenn in diesem Objekt nichts Überflüssiges drin wäre: nur das, was für den Datentransfer nötig ist. Genau das ist eine DTO-Klasse.

public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Klassisches DTO: nur Properties, keine Logik

Das war's! Keine Business-Logik, keine komplizierten Methoden – nur Properties.

Klassen nur für Datentransfer

Stell dir vor, du machst Pizza-Lieferung. Dein Kurier (DTO) muss nicht Pizza backen, Bestellungen annehmen oder das Fahrrad reparieren können. Alles, was er tun muss: die Box von Punkt A nach Punkt B bringen. So ist auch die DTO-Klasse: Ihre Aufgabe ist es, einfache Daten zu speichern. Sie muss sich nicht selbst validieren können, sie muss nicht wissen, wie sie sich im UI darstellt – nur speichern.

  • DTOs sind „Menschen ohne Beruf“ oder „Objekte ohne Logik“, sie tragen einfach Daten herum.
  • Man nutzt sie, damit die Business-Logik nicht mit Details vom Datentransfer zugemüllt wird: Je kleiner der Koffer, desto leichter zu tragen.

2. Probleme mit normalen DTO-Klassen in der Praxis

Man denkt, jetzt ist alles easy: Klasse mit Auto-Properties deklariert und das Leben ist schön! Aber so einfach ist es nicht.

Mutierbarkeit der Daten: Was kann schiefgehen?

public class ProductDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}
DTO mit öffentlichen set – Daten lassen sich leicht versehentlich ändern

Stell dir jetzt vor, irgendwo im Code wird eine Property aus Versehen geändert:

ProductDto dto = new ProductDto { Id = 1, Name = "Milch" };
SomeApiProcess(dto);
dto.Name = "Kefir"; // Ups!

Weil alle Properties einen öffentlichen set haben, kann man das Objekt easy aus Versehen kaputt machen oder – noch schlimmer – es an einer Stelle ändern, was dann unerwartet durchs ganze System „wandert“. Das wird richtig problematisch, wenn Objekte von vielen Modulen und Threads benutzt werden.

Kopieren und Vergleichen – endlose Routine

Angenommen, du willst eine Kopie von ProductDto machen, aber nur den Namen ändern.

var otherDto = new ProductDto { Id = dto.Id, Name = "Quark" };

Wenn es viele Properties gibt, wird das zu einer langweiligen und potenziell fehleranfälligen Copy-Paste-Orgie.

Und wenn du die Objekte auch noch vergleichen willst? Dann musst du Equals und GetHashCode überschreiben – was oft halbherzig gemacht wird oder komplett vergessen wird.

Unveränderlichkeit – nichts für „einfache“ Klassen

Meistens sollten DTOs unveränderlich sein: Man bekommt die Daten einmal – und ändert sie dann nicht mehr. Aber eine Klasse mit Auto-Properties taugt dafür nicht: Jeder kann jedes Feld ändern.

3. Wie sieht das in einem großen Projekt aus?

Schauen wir uns ein Beispiel aus dem echten Leben an.

Du bist Entwickler in einem riesigen Online-Shop. Du musst Bestell-Infos übers Netzwerk schicken. Dafür baust du so eine Klasse:

public class OrderDto
{
    public int Id { get; set; }
    public string Customer { get; set; }
    public double Total { get; set; }
}

Alles läuft, bis plötzlich ein paar Hunderttausend Bestellungen pro Woche reinkommen. Jede Menge Microservices schieben DTOs hin und her, jemand vergisst irgendwo, dass Total berechnet werden muss und nicht einfach gesetzt werden darf, jemand ändert aus Versehen Customer nach dem Senden... Und schon hast du Bugs, die echt schwer zu finden sind.

Wie kann man das besser machen?

  • Nur get-Properties verwenden (ohne set).
  • Einen speziellen Konstruktor bauen, damit Werte nur beim Erstellen gesetzt werden können.
  • Equals und GetHashCode überschreiben…

Merkst du, worauf das hinausläuft? Was eigentlich ein simpler „Koffer“ sein sollte, wird plötzlich zu einem undurchsichtigen Monster.

4. Kurzer Überblick über typische DTO-Probleme

In kleinen Projekten kann man Bugs, die durch DTO-Änderungen entstehen, noch „mit der Hand fangen“. In großen – ist das eine Katastrophe. DTOs werden für Auth, Geldtransfers, Bestellabwicklung genutzt – jede illegale Datenänderung kann zu Geldverlusten oder (verdammt nochmal!) sogar zu Strafverfahren führen.

Hier eine „Spickzettel“-Liste von Problemen, die fast jeder hat, der DTOs als Klassen schreibt:

Problem Beschreibung
Mutierbarkeit Daten lassen sich überall leicht ändern – Buggefahr (besonders bei Multithreading)
Klonen Objekte zu kopieren ist umständlich, oft wird das per Hand gemacht
Vergleich Standardmäßig wird nach Referenz verglichen, nicht nach Wert
with-Kopien unterstützen Man möchte mit Änderung einzelner Daten kopieren, ohne alles per Hand zu machen
Unklare Verschachtelung Verschachtelte DTOs müssen komplett kopiert werden, das ist nervig

5. Beispiel: Datentransfer zwischen Schichten in unserer App

Angenommen, wir haben eine ToDo-App, in der wir Aufgaben speichern. Früher hätten wir so eine Klasse gehabt:

public class TaskDto
{
    public int Id { get; set; }
    public string Description { get; set; }
    public DateTime DueDate { get; set; }
}

Wir haben TaskDto aus einer Datei gelesen, dann in eine Collection gepackt, dann auf dem Screen angezeigt. Alles war cool... bis jemand aus einem anderen Modul aus Versehen DueDate direkt vor der Serialisierung geändert hat – und der User war verwirrt.

7. Geht das auch besser?

Ja! Und nicht nur das – es sollte sogar besser gemacht werden! Um diese Probleme zu lösen, gibt es in C# eine spezielle Konstruktion – record. Damit wird der ganze Ärger mit DTO-Klassen fast magisch gelöst.

Was macht record:

  • Standardmäßig wertorientiert: Objekte werden nach Inhalt verglichen, nicht nach Referenz.
  • Unveränderlichkeit: Man kann nur get-Properties angeben, Werte werden über den Konstruktor gesetzt.
  • Easy Cloning: Mit with kann man mit Änderungen kopieren.
  • Automatische Methoden für Vergleich und Ausgabe werden generiert.
public record TaskDto(int Id, string Description, DateTime DueDate);
DTO auf Basis von record: kompakt, sicher, bequem!

Cool, oder? Auf jeden Fall! Aber dazu mehr in der nächsten Vorlesung.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION