Files
symfony-architektur/architektur-patterns.md
T

5.5 KiB

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

namespace App\Logic\Common;

/**
 * @template TRequest
 * @template TResponse
 */
interface UseCaseInterface
{
    /**
     * @param TRequest $request
     * @return TResponse|void
     */
    public function execute(mixed $request): mixed;
}

Implementierung

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

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. Hier erfolgt primär die Cache-Logik ("Cache-Aside") und die Koordination von Providern.
- **Verantwortung**: Entscheidung, ob Daten aus dem Cache oder frisch vom Provider geladen werden müssen.

#### Provider (Data Layer)
- **Zweck**: Führt die technische Abfrage gegen die Infrastruktur aus (DB, API).
- **Rückgabewert**: Liefert die **Kern-Business-Models** zurück, nicht notwendigerweise die technischen Entities der Datenbank.

### 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

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 []; 
    }
}