256 lines
8.8 KiB
Markdown
256 lines
8.8 KiB
Markdown
# Architektur Patterns
|
|
|
|
Diese Datei definiert die konkreten Implementierungsmuster für die verschiedenen Layer der Anwendung.
|
|
|
|
## 1. UseCase Pattern (`src/Logic`)
|
|
|
|
Das UseCase Pattern bildet das Herzstück der Business-Logik. Jeder Anwendungsfall wird als eigenständige Klasse implementiert, um eine klare Trennung der Verantwortlichkeiten und eine hohe Testbarkeit zu gewährleisten.
|
|
|
|
### Struktur & Definition
|
|
- **Interface**: Alle UseCases müssen das `UseCaseInterface` implementieren.
|
|
- **Methode**: Die primäre Logik liegt in der Methode `execute()`.
|
|
- **Eingabe/Ausgabe**: Daten werden strikt über DTOs (Data Transfer Objects) ausgetauscht.
|
|
- **Rückgabewert**: Ein UseCase gibt entweder ein Response-DTO zurück oder `void` (bei reinen Schreiboperationen).
|
|
- **Fehlerbehandlung**: Business-Fehler werden über spezifische **Domain-Exceptions** signalisiert.
|
|
|
|
### DTOs (Data Transfer Objects)
|
|
- **Typ**: Readonly-Klassen (PHP 8.2+).
|
|
- **Namenskonvention**: Suffix `Request` für Eingabe, `Response` für Ausgabe.
|
|
- **Typisierung**: Maximale native Typisierung; PHPDocs nur bei komplexen Types (z.B. Generics in Arrays).
|
|
|
|
### Namenskonvention & Verzeichnisstruktur
|
|
- **Klassennamen**: Suffix `UseCase`.
|
|
- **Pfad**: `src/Logic/{Module}/{Feature}/{Typ}/...`
|
|
|
|
**Beispiel-Struktur:**
|
|
- `src/Logic/Sales/Order/UseCase/CreateOrderUseCase.php`
|
|
- `src/Logic/Sales/Order/Dto/CreateOrderRequest.php`
|
|
- `src/Logic/Sales/Order/Dto/CreateOrderResponse.php`
|
|
|
|
### Code Beispiel
|
|
|
|
#### Interface
|
|
```php
|
|
namespace App\Logic\Common;
|
|
|
|
/**
|
|
* @template TRequest
|
|
* @template TResponse
|
|
*/
|
|
interface UseCaseInterface
|
|
{
|
|
/**
|
|
* @param TRequest $request
|
|
* @return TResponse|void
|
|
*/
|
|
public function execute(mixed $request): mixed;
|
|
}
|
|
```
|
|
|
|
#### Implementierung
|
|
```php
|
|
namespace App\Logic\Sales\Order\UseCase;
|
|
|
|
use App\Logic\Common\UseCaseInterface;
|
|
use App\Logic\Sales\Order\Dto\CreateOrderRequest;
|
|
use App\Logic\Sales\Order\Dto\CreateOrderResponse;
|
|
use App\Logic\Sales\Order\Exception\InsufficientStockException;
|
|
|
|
readonly class CreateOrderUseCase implements UseCaseInterface
|
|
{
|
|
public function execute(mixed $request): CreateOrderResponse
|
|
{
|
|
if (!$request instanceof CreateOrderRequest) {
|
|
throw new \InvalidArgumentException('Invalid request type');
|
|
}
|
|
|
|
// Business Logik hier...
|
|
if ($this->stockTooLow()) {
|
|
throw new InsufficientStockException();
|
|
}
|
|
|
|
return new CreateOrderResponse(orderId: '123');
|
|
}
|
|
|
|
private function stockTooLow(): bool { return false; }
|
|
}
|
|
```
|
|
|
|
#### DTOs
|
|
```php
|
|
namespace App\Logic\Sales\Order\Dto;
|
|
|
|
readonly class CreateOrderRequest
|
|
{
|
|
public function __construct(
|
|
public string $customerId,
|
|
public array $items,
|
|
) {}
|
|
}
|
|
|
|
readonly class CreateOrderResponse
|
|
{
|
|
public function __construct(
|
|
public string $orderId,
|
|
) {}
|
|
}
|
|
|
|
## 2. Read Pattern (`src/Logic` $\rightarrow$ `src/Data`)
|
|
|
|
Das Read Pattern beschreibt den Weg einer Datenabfrage von der UI bis zur Datenquelle. Es stellt sicher, dass die UI-Layer niemals direkt auf die Data Layer zugreift und Business-Modelle konsistent zurückgegeben werden.
|
|
|
|
### Der Datenfluss
|
|
`UI Layer` $\rightarrow$ **BusinessQuery** $\rightarrow$ **Manager** $\rightarrow$ **Provider**
|
|
|
|
#### BusinessQuery (Logic Layer)
|
|
- **Zweck**: Dient als fachlicher Einstiegspunkt für Leseoperationen. Sie orchestriert Manager oder andere Queries, um ein Endresultat zu formen.
|
|
- **Struktur**: Implementiert das gleiche Muster wie UseCases (`execute()` Methode).
|
|
- **Input/Output**: Nutzt Request-DTOs (besonders für Filter und Pagination) und gibt Response-DTOs zurück.
|
|
- **Pfad**: `src/Logic/{Module}/{Feature}/Query/...`
|
|
|
|
#### Manager (Logic Layer)
|
|
- **Zweck**: Steuert die Bereitstellung der Daten und koordiniert Schreibvorgänge.
|
|
- **Schnittstelle**: Besitzt ein eigenes Interface in der Logic Layer zur Gewährleistung von Testbarkeit und Austauschbarkeit.
|
|
- **Verantwortung**:
|
|
- Read: Entscheidung zwischen Cache (Cache-Aside) und Provider.
|
|
- Write: Triggerung der Cache-Invalidierung nach erfolgreichen Änderungen.
|
|
- **Zustand**: Strikt zustandslos (stateless).
|
|
|
|
|
|
...
|
|
102: ### Der Datenfluss
|
|
103: `UI Layer` $\rightarrow$ **BusinessQuery** $\rightarrow$ **Manager** $\rightarrow$ **Provider**
|
|
104:
|
|
105: #### BusinessQuery (Logic Layer)
|
|
106: - **Zweck**: Dient als fachlicher Einstiegspunkt für Leseoperationen. Sie orchestriert Manager oder andere Queries, um ein Endresultat zu formen.
|
|
107: - **Struktur**: Implementiert das gleiche Muster wie UseCases (`execute()` Methode).
|
|
108: - **Input/Output**: Nutzt Request-DTOs (besonders für Filter und Pagination) und gibt Response-DTOs zurück.
|
|
109: - **Pfad**: `src/Logic/{Module}/{Feature}/Query/...`
|
|
110:
|
|
111: #### Manager (Logic Layer)
|
|
112: - **Zweck**: Steuert die Bereitstellung der Daten. Hier erfolgt primär die Cache-Logik ("Cache-Aside") und die Koordination von Providern.
|
|
113: - **Verantwortung**: Entscheidung, ob Daten aus dem Cache oder frisch vom Provider geladen werden müssen.
|
|
114:
|
|
115: #### Provider (Data Layer)
|
|
116: - **Zweck**: Führt die technische Abfrage gegen die Infrastruktur aus (DB, API).
|
|
117: - **Schnittstelle**: Die Provider-Interfaces liegen zwingend in der **Logic Layer**, um Dependency Inversion zu gewährleisten.
|
|
118: - **Rückgabewert**: Liefert ausschließlich **Kern-Business-Models** zurück.
|
|
119: - Einfache Listen $\rightarrow$ `array` (mit PHPDoc `@return Model[]`).
|
|
120: - Paginierten Listen $\rightarrow$ Ein Wrapper-Objekt (z. B. `PaginatedCollection`), das Daten und Metadaten enthält.
|
|
121: - **Fehlerbehandlung**: Wirft im Fehlerfall direkt **Domain-Exceptions**.
|
|
122: - **Parameter**: Nutzt für einfache Lookups primitive Typen (`string`, `int`), für komplexe Abfragen DTOs.
|
|
123: - **Granularität**: Ein Provider pro Business-Model.
|
|
124:
|
|
125: ### Code Beispiel
|
|
...
|
|
|
|
### Code Beispiel
|
|
|
|
#### BusinessQuery & DTOs
|
|
```php
|
|
namespace App\Logic\Sales\Order\Query;
|
|
|
|
use App\Logic\Common\UseCaseInterface;
|
|
use App\Logic\Sales\Order\Dto\GetOrdersRequest;
|
|
use App\Logic\Sales\Order\Dto\GetOrdersResponse;
|
|
use App\Logic\Sales\Order\Manager\OrderManager;
|
|
|
|
readonly class GetOrdersQuery implements UseCaseInterface
|
|
{
|
|
public function __construct(private OrderManager $orderManager) {}
|
|
|
|
public function execute(mixed $request): GetOrdersResponse
|
|
{
|
|
if (!$request instanceof GetOrdersRequest) {
|
|
throw new \InvalidArgumentException('Invalid request type');
|
|
}
|
|
|
|
$orders = $this->orderManager->findOrders($request->filter, $request->page);
|
|
|
|
return new GetOrdersResponse(orders: $orders);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Manager & Provider
|
|
```php
|
|
namespace App\Logic\Sales\Order\Manager;
|
|
|
|
use App\Data\Sales\Order\Provider\OrderProvider;
|
|
|
|
readonly class OrderManager
|
|
{
|
|
public function __construct(private OrderProvider $orderProvider) {}
|
|
|
|
public function findOrders(array $filters, int $page): array
|
|
{
|
|
// Cache-Logik hier...
|
|
return $this->orderProvider->fetchOrders($filters, $page);
|
|
}
|
|
}
|
|
|
|
// --- Data Layer ---
|
|
namespace App\Data\Sales\Order\Provider;
|
|
|
|
readonly class OrderProvider
|
|
{
|
|
public function fetchOrders(array $filters, int $page): array
|
|
{
|
|
// DB Abfrage und Mapping zu Business-Models
|
|
return [];
|
|
}
|
|
}
|
|
|
|
## 3. Write Pattern (`src/Logic` $\rightarrow$ `src/Data`)
|
|
|
|
Das Write Pattern definiert den Weg von einer Zustandsänderung hin zur Persistenz. Es stellt sicher, dass Schreiboperationen atomar erfolgen und die Integrität der Business-Models gewahrt bleibt.
|
|
|
|
### Der Datenfluss
|
|
`UI Layer` $\rightarrow$ **UseCase** $\rightarrow$ **Manager** (optional) $\rightarrow$ **Processor**
|
|
|
|
#### Processor (Data Layer)
|
|
- **Zweck**: Übernimmt die physische Persistierung eines Business-Models.
|
|
- **Schnittstelle**: Das `ProcessorInterface` liegt zwingend in der **Logic Layer** (Dependency Inversion).
|
|
- **Input**: Ein Business-Model.
|
|
- **Rückgabewert**: Das aktualisierte/neu erstellte Business-Model (z.B. zur Rückgabe von generierten IDs).
|
|
- **Fehlerbehandlung**: Wirft im Fehlerfall direkt **Domain-Exceptions**.
|
|
- **Strategie**: Verfolgt den "All or Nothing" Ansatz; die übergeordnete Transaktion wird vom UseCase gesteuert.
|
|
- **Granularität**: Fokus auf eine einzelne Entität pro Processor.
|
|
|
|
### Code Beispiel
|
|
|
|
#### Processor Interface & Implementierung
|
|
```php
|
|
namespace App\Logic\Sales\Order;
|
|
|
|
use App\Logic\Sales\Order\Model\Order;
|
|
|
|
interface OrderProcessorInterface
|
|
{
|
|
public function save(Order $order): Order;
|
|
public function delete(string $id): void;
|
|
}
|
|
|
|
// --- Data Layer ---
|
|
namespace App\Data\Sales\Order\Processor;
|
|
|
|
use App\Logic\Sales\Order\OrderProcessorInterface;
|
|
use App\Logic\Sales\Order\Model\Order;
|
|
|
|
readonly class OrderProcessor implements OrderProcessorInterface
|
|
{
|
|
public function save(Order $order): Order
|
|
{
|
|
// Persistenz-Logik (DB/API) ...
|
|
return $order; // Hier ggf. mit generierter ID zurückgeben
|
|
}
|
|
|
|
public function delete(string $id): void
|
|
{
|
|
// Lösch-Logik ...
|
|
}
|
|
}
|
|
```
|
|
```
|
|
```
|