Wie bereits richtig erahnt habt, wird es in diesem Artikel um Entitäten, Data Transfer Objekte und Value Objekte gehen. Wir werden herausstellen, was diese Objekte sind und in welchem Zusammenhang Du sie in Deinem Projekt verwenden solltest.
In den letzten Projekten sorgten Entitäten, Data Transfer Objekte und Value Objekte in der Softwarearchitektur immer mal wieder für Verwirrung. Dabei erfüllen die verschiedenen Objekte sehr unterschiedliche Rollen, auch wenn sie sich manchmal sehr ähneln. Eine klare Trennung wird Deinen Code robuster, verständlicher und langfristig wartbarer machen.
Entitäten
Merkmale:
- Besitzen immer einen eindeutigen Identifier, wie z.B. eine User ID oder eine Order ID.
- Zwei Entitäten können unterschiedliche Daten besitzen und trotzdem dieselbe Entität sein, wenn der Identifier dieser Objekte gleich ist.
- Stellen Business Logik / Geschäftsobjekte dar und können sich im Laufe der Zeit ändern.
- Werden oft in ORMs wie Doctrine, Propel oder Laravel Eloquent verwendet.
Entitäten haben im direkten Vergleich zu Data Transfer Objekten und Value Objekten immer eine Identität. Ein klassisches Beispiel wäre ein Kunde in einem Webshop, der lediglich seine Adresse ändert, aber dennoch der gleiche Kunde bleibt.
<?php
declare(strict_types=1);
namespace MMNewmedia\Entity;
use Doctrine\ORM\Mapping;
#[ORM\Entity]
#[ORM\Table(name: 'products')]
class Product
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $name;
// ...
}
Das gezeigte PHP Beispiel zeigt eine Doctrine Entität mit einem Identifier $id.
Value Objects
Merkmale:
- Haben keine Identität. Sie sind nur durch ihre Werte definiert.
- Sind weitestgehend immutable: Änderungen erzeugen ein neues Objekt (Wither Pattern)
- Value Objects verhalten sich wie einzelne Werte: Geld, Datumswerte oder -spannen, Adressen oder Koordinaten
- Zwei Value Objekte sind gleich, wenn alle ihre Werte gleich sind.
Zwei Geldwerte mit dem gleichen Wert gelten als Identisch, auch wenn sie in unterschiedlichen Objekten vorkommen. Value Objects werden auch oft im Zusammenhang mit XML verwendet. In einem meiner Projekte rund um das Thema eInvoicing (EN16931) werden oft XML Elemente als Value Objekte interpretiert, weil sie nur einen Wert und eventuell Attribute besitzen.
<?php
declare(strict_types=1);
namespace MMNewmedia\CrossIndustryInvoice\UnqualifiedDataType;
use MMNewmedia\Xsd;
interface ValuableInterface
{
public function getValue(): mixed;
public function withValue(mixed $value): self;
}
trait ValuableTrait
{
protected mixed $value;
public function getValue(): mixed
{
return $this->value;
}
public function withValue(mixed $value): self
{
return clone $this with {
value: $value
};
}
}
#[Xsd\ComplexType]
#[Xsd\SimpleContent]
#[Xsd\Extension(base: 'xsd:decimal')]
final readonly class AmountType implements ValuableInterface
{
use ValuableInterface;
public function __construct(
#[Xsd\Attribute(name: 'currencyID')]
public ?string $currencyId = null,
#[Xsd\Attribute(name: 'currencyCodeListVersionID')]
public ?string $currencyCodeListVersionId = null,
) {}
}
// Beispiel: <ram:PaidAmount currencyID="EUR">7.95</ram:PaidAmount>
// $paidAmount = new AmountType(currencyId: 'EUR')->withValue(7.95);
Das Beispiel zeigt ein Value Objekt, welches den Wert eines XML Elements des komplexen XSD Typen AmountType repräsentiert. Es geht hier um Geldwerte, die immer einen Wert repräsentieren. Ein Identifier, wie bei einer Entität, fehlt hier komplett. Konkret stammt dieses Beispiel aus einem eigenen Repository, welches die Cross Industry Invoice Norm darstellt.
Data Transfer Objekte (DTO)
Merkmale:
- Werden ausschließlich beim Transport von Daten zwischen Schichten oder Services genutzt
- Stellen keinerlei Business Logik dar
- Müssen nicht identisch mit den Daten einer Entität oder eines Value Objekts sein
- Die Modellierung erfolgt oft, um API-Requests und Responses darzustellen
- Können sehr flach, serialisierbar und auf das Nötigste reduziert sein
Ein klassisches Beispiel wäre zum Beispiel ein UserResponse Data Transfer Objekt, welches ausschließlich die Daten eines Users beinhaltet, welche von der API nach außen gegeben werden soll – oft anders strukturiert, als die Entität eines Users.
Ein kurzer Vergleich
| Konzept | Identität | Mutable | Zweck |
|---|---|---|---|
| Entität | ja | ja | Business Logik / Geschäftsobjekt |
| Value Objekt | nein | nein | Wertrepräsentation |
| Data Transfer Objekt | nein | egal | Datentransport |
Warum ist diese Trennung wichtig?
Die Trennung zwischen Entitäten, Data Transfer Objekten und Value Objekten ist wichtig, um die Bereiche der Anwendung sauber trennen zu können. Das System wird durch diese Rollen stabiler, verständlicher und testbarer.
- Eintitäten modellieren die Realität Deines Anwendungsfalls
- Value Objekte machen Dein Domain-Modell präziser und sicherer
- Data Transfer Objekte schützen das Domain-Modell vor äußeren Abhängigkeiten
Fazit
Ich persönlich versuche die saubere Trennung zwischen Entitäten, Data Transfer Objekten und Value Objekten immer anzuwenden, weil sie schlichtweg Sinn ergibt. Eine saubere Architektur in Deinem Software Projekt ist wichtig und wird über kurz oder lang dazu führen, dass das Projekt stabiler läuft. Fehler werden sich einfacher beheben lassen, da sehr viel genauer getestet werden kann. Erweiterungen sind einfacher zu implementieren, da sie genau auf den Anwendungsfall zugeschnitten werden können.
Selbst in den letzten Legacy Projekten, in denen ich als Freelancer tätig war, sind der Kunde und ich oft dazu übergegangen den über Jahre gewachsenen historischen Code neu zu strukturieren und zwischen Entitäten, Data Transfer Objekten und Value Objekten zu unterscheiden. Je weiter diese Architektur implementiert wurde, desto schneller wurde das Refactoring des Projekts.
Nutzt Du die Unterscheidung zwischen Entitäten, Data Transfer Objekten und Value Objekten bereits in Deinem Projekt? Lass es mich einfach in einem Kommentar wissen.