DTO vs. Value Object: Die Verwirrung in PHP entwirren
Wenn Sie sich eine Zeit lang in modernen PHP-Kreisen aufgehalten haben, haben Sie beide Begriffe gehört: DTO und Value Object. Sie klingen ähnlich, beide sind einfache Klassen, und beide halten Daten. Entwickler verwenden sie oft austauschbar — und das ist ein Fehler, der zu einer verworrenen Architektur führt.
In diesem Artikel entwirren wir die Verwirrung ein für alle Mal. Sie werden lernen, wofür jedes Muster gedacht ist, wie sie sich unterscheiden und — am wichtigsten — wie Sie entscheiden, welches Sie in einer bestimmten Situation tatsächlich brauchen.
Die Wurzel der Verwirrung
Sowohl DTOs als auch Value Objects teilen mehrere Eigenschaften, die sie leicht verwechselbar machen:
- Beide sind kleine Klassen.
- Beide halten Daten.
- Beide sind oft unveränderlich.
- In einfachen Fällen können sie nahezu identisch aussehen.
Aber sie existieren aus völlig unterschiedlichen Gründen. Sie zu verwechseln ist wie eine Versandkiste mit einer Münze zu verwechseln — beides sind Objekte, aber ihr Zweck, ihre Regeln und ihr Lebenszyklus sind völlig unterschiedlich.
Was ist ein DTO?
Ein Data Transfer Object ist eine Klasse, deren Aufgabe es ist, Daten zwischen Schichten oder Prozessen zu transportieren. Das ist alles. Es hat kein Verhalten außer dem Abrufen und Setzen von Werten (oder dem Bereitstellen von readonly-Eigenschaften).
Typische Anwendungsfälle:
- Rückgabe von Daten von einem Controller an eine View.
- Übergabe von Eingaben aus einer HTTP-Anfrage an einen Service.
- Serialisierung von Daten in JSON für eine API-Antwort.
- Zuordnung von Datenbankzeilen zu einer anwendungsfreundlichen Struktur.
Ein einfaches DTO sieht so aus:
Beachten Sie, was fehlt: keine Validierung, keine Geschäftsregeln, keine Domänenbedeutung. Es ist ein Container. Nicht mehr.
Was ist ein Value Object?
Ein Value Object ist eine Klasse, die ein Konzept aus Ihrer Domäne repräsentiert. Es wird vollständig durch seine Werte definiert, hat keine Identität und — entscheidend — es erzwingt seine eigene Gültigkeit und kapselt Verhalten im Zusammenhang mit diesen Werten.
Typische Anwendungsfälle:
- Darstellung von
Email,Money,UserId,PhoneNumberoderDateRange. - Gewährleistung, dass ein Wert überall, wo er verwendet wird, gültig ist.
- Kapselung von Domänenregeln wie Währungsabgleich oder Datumsüberschneidungen.
- Beseitigung von Primitivenbesessenheit in Ihrem Domänenmodell.
Ein einfaches Value Object sieht so aus:
Beachten Sie, was hier vorhanden ist: Validierung, Normalisierung, Gleichheit und Absicht. Das Objekt hält nicht nur Daten — es ist ein Konzept.
Direkter Vergleich
- Zweck: DTO — Daten transportieren; VO — ein Domänenkonzept repräsentieren.
- Verhalten: DTO — keines (nur Getter/Setter oder readonly-Props); VO — kann Domänenverhalten enthalten.
- Validierung: DTO — extern validiert (z. B. in einem Form Request); VO — validiert sich selbst bei der Erstellung.
- Identität: DTO — irrelevant; VO — durch Werte definiert, keine Identität.
- Gleichheit: DTO — normalerweise nicht implementiert; VO — Gleichheit basierend auf Werten, oft über
equals(). - Unveränderlichkeit: DTO — optional; VO — erforderlich.
- Schicht: DTO — Anwendungsschicht (Grenzen); VO — Domänenschicht.
- Wahrheitsquelle: DTO — externe Daten; VO — Domänenregeln.
Ein konkretes Beispiel
Stellen Sie sich vor, Sie bauen eine Benutzerregistrierungsfunktion. Sie erhalten eine HTTP-Anfrage mit Rohdaten. Hier glänzt ein DTO:
Sobald die Validierung bestanden ist, konvertieren Sie das DTO in ein Domänenobjekt, das Value Objects verwendet:
Sehen Sie den Ablauf? Das DTO transportierte Rohdaten über die Anwendungsgrenze. Die Value Objects schützten die Domäne vor ungültigem Zustand. Jedes Muster erfüllte seine Aufgabe — und keines konnte das andere ersetzen.
Wo Menschen Fehler machen
Verwirrung zwischen DTOs und VOs führt typischerweise zu einem von zwei Fehlern:
- Ein DTO als Value Object verwenden. Sie erhalten Validierungslogik, die über Services verstreut ist, weil das DTO nicht validiert. Die Domäne bleibt ungeschützt.
- Ein Value Object als DTO verwenden. Sie zwingen Domänenkonzepte in Transportbelange — indem Sie Serialisierungsmethoden, „toArray()" oder zusätzliche Konstruktoren hinzufügen — und verschmutzen Ihre Domäne mit Infrastrukturlogik.
Beide Fehler untergraben die Grenzen, von denen saubere Architektur abhängt.
Wie man entscheidet: Eine einfache Heuristik
Fragen Sie sich: Repräsentiert diese Klasse ein Konzept mit Regeln, oder transportiert sie nur Daten?
- Wenn sie ein Domänenkonzept repräsentiert (E-Mail, Geld, Bestell-ID) → Value Object.
- Wenn sie Daten zwischen Schichten transportiert (Formulareingabe, API-Payload, Abfrageergebnis) → DTO.
Eine andere Denkweise:
- Ein DTO ist eine Form — es definiert, welche Felder existieren.
- Ein Value Object ist eine Bedeutung — es definiert, was der Wert ist und was er tun kann.
Kann ein DTO Value Objects enthalten?
Ja, und das ist oft eine gute Idee. Ein DTO kann Value Objects anstelle von Primitiven transportieren, wenn die Grenze dies erfordert:
Das ist in Ordnung, solange das DTO kein Verhalten hinzufügt und die Value Objects weiterhin ihre Regeln durchsetzen. Das DTO transportiert weiterhin; die VOs schützen weiterhin.
Kann ein Value Object direkt in der Serialisierung verwendet werden?
In vielen Anwendungen werden Value Objects über benutzerdefinierte Normalisierer oder durch Bereitstellung einer toString()- oder toNative()-Methode in JSON serialisiert. Das ist akzeptabel — aber vermeiden Sie es, Persistenz- oder Transportbelange mit den Kernaufgaben des VO zu vermischen. Wenn die Serialisierungslogik schwer wird, legen Sie sie in einen separaten Serializer und nicht in das VO selbst.
Best Practices
- Halten Sie DTOs langweilig. Keine Validierung, keine Geschäftsregeln, keine versteckte Magie. Nur Daten und Konstruktoren.
- Halten Sie Value Objects streng. Validieren Sie bei der Erstellung, erzwingen Sie Invarianten, bieten Sie nützliches Verhalten.
- Verwischen Sie die Grenzen nicht. Konvertieren Sie DTOs an der Grenze Ihrer Anwendung in Domänenobjekte.
- Wählen Sie überall Unveränderlichkeit. Beide Muster profitieren von
readonlyund Constructor-Promotion in PHP 8. - Lassen Sie die Domäne sprechen. Wo immer ein Primitiv Bedeutung tragen könnte, ersetzen Sie es durch ein Value Object.
Fazit
DTOs und Value Objects sind beide einfach, leistungsstark und oft verwechselt. Die Unterscheidung ist nicht akademisch — sie ist architektonisch. Ein DTO ist ein Kurier; ein Value Object ist ein Bürger Ihrer Domäne. Eines bewegt Daten, das andere erzwingt Bedeutung.
Verwenden Sie DTOs an den Grenzen Ihrer Anwendung, um Daten hinein und hinaus zu transportieren. Verwenden Sie Value Objects innerhalb Ihrer Domäne, um ungültige Zustände unmöglich zu machen und Geschäftsregeln klar auszudrücken. Wenn Sie diese Rollen getrennt halten, wird Ihre Codebasis einfacher zu testen, einfacher zu verstehen und viel schwerer zu brechen.
Behandeln Sie sie nicht als austauschbar. Behandeln Sie sie als komplementär — und Ihre Architektur wird es Ihnen danken.