From d3b2fad8a94d8a8e2e3af9029a64cd592f0c2730 Mon Sep 17 00:00:00 2001 From: Jens Date: Fri, 17 Jul 2026 11:38:25 +0200 Subject: [PATCH] docs: document full and partial update patterns for readonly Models --- architektur-patterns.md | 78 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/architektur-patterns.md b/architektur-patterns.md index 39cc5c5..a480f92 100644 --- a/architektur-patterns.md +++ b/architektur-patterns.md @@ -594,7 +594,10 @@ Um die UseCases schlank zu halten, findet die Transformation von Eingabe-DTOs (` - **Ort**: `src/Logic/{Module}/{Feature}/Mapping/` - **Verantwortung**: Abbildung der Struktur von einem DTO auf ein Business Model (einschließlich Child-Models). -- **Dependency Rule**: Diese Mappers liegen zwingend in der Logic Layer und kennen keine Entitys oder Provider. +- **Dependency Rule**: Diese Mapper liegen zwingend in der Logic Layer und kennen keine Entities oder Provider. + +#### Factory-Pattern (Create-Szenario) +Für neue Objekte liefert die Factory ein komplett neues, valides Business-Model. ```php namespace App\Logic\Sales\Order\Mapping; @@ -610,7 +613,7 @@ readonly class OrderModelFactory { return new Order( customerId: $request->customerId, - // Delegierung der Child-Objekte + // Delegierung der Child-Objects items: array_map( fn($data) => new OrderItem(productId: $data['productId'], quantity: $data['quantity']), $request->items @@ -621,6 +624,77 @@ readonly class OrderModelFactory } ``` +#### Factory-Pattern für Updates (Full Rebuild) +Das einfachste Verfahren. Die Factory erhält den Request-DTO und erstellt wie gewohnt ein neues Model mit allen Daten des Requests — inklusive der ID des bestehenden Objekts. Da Models im Logic Layer rein funktional sind, ist das Erstellen eines neuen Modells billiger als ein Objekt zu kopieren und zu manipulieren. + +```php +namespace App\Logic\Sales\Order\Mapping; + +use App\Logic\Sales\Order\Dto\UpdateOrderRequest; // Enthält id:, email:, amount: ... +use App\Logic\Sales\Order\Model\Order; + +readonly class OrderModelFactory +{ + public function updateFromRequest(UpdateOrderRequest $request): Order + { + return new Order( + id: $request->id, // ID aus dem Request übernehmen + customerEmail: $request->customerEmail ?? null, + totalAmount: $request->totalAmount, + // ... restliche Felder (bei Partial Updates ggf. per Provider ergänzt) + ); + } +} +``` + +#### Kopier-Pattern für Partial-Updates (Model.copyWith) +Wenn nur bestimmte Felder eines bestehenden Business-Models geändert werden sollen, empfiehlt es sich, eine **copyWith**-Methode im Model selbst zu implementieren: + +```php +namespace App\Logic\Sales\Order\Model; + +readonly class Order +{ + public function __construct( + public string $id, + public string $customerEmail, + public float $totalAmount, + public int $version, + ) {} + + /** + * Erstellt eine Kopie und überschreibt nur die mitgegebenen Felder. + */ + public function with( + ?string $customerEmail = null, + ?float $totalAmount = null, + ): self { + return new self( + id: $this->id, + customerEmail: $customerEmail ?? $this->customerEmail, + totalAmount: $totalAmount ?? $this->totalAmount, + version: 0, // Wird beim Speichern durch den Data Layer aktualisiert. + ); + } + + /** + * Erstellt eine Version-Kopie für ein neues Update (Concurrency Handling). + */ + public function nextVersion(): self + { + return new self( + id: $this->id, + customerEmail: $this->customerEmail, + totalAmount: $this->totalAmount, + version: $this->version + 1, + ); + } +} + +// Verwendung im UseCase oder in der Factory +$updatedOrder = $existingOrder->with(customerEmail: 'new@example.com', totalAmount: 123.45); +``` + ### Code Beispiel (Data Layer Mapping) #### Mapper Implementierung