Zum Hauptinhalt springen

Anwendungs-Schicht

Die Brücke zwischen Domäne und äußerer Welt: Orchestrierung von Anwendungsfällen.

Die Anwendungs-Schicht in einer DDD-Symfony-Anwendung

Die Anwendungs-Schicht dient als Brücke zwischen der Domain-Schicht und der äußeren Welt. Sie orchestriert den Fluss von Daten und Befehlen zwischen Benutzern oder externen Systemen und der Domäne. Diese Schicht enthält Anwendungsfälle (Use Cases) und koordiniert die Ausführung von Domänenlogik.

Struktur der Anwendungs-Schicht

src/
└── Application/
├── Command/ # Befehle für Schreiboperationen
│ ├── Handler/ # Command-Handler
│ └── ... # Command-Objekte
├── Query/ # Abfragen für Leseoperationen
│ ├── Handler/ # Query-Handler
│ └── ... # Query-Objekte
├── DTO/ # Data Transfer Objects
├── Service/ # Anwendungsservices
├── EventHandler/ # Handler für Domänen-Events
└── Validator/ # Validierungslogik für Eingabedaten

Command und Command Handler (CQRS-Ansatz)

Der Command Query Responsibility Segregation (CQRS) Ansatz trennt Lese- und Schreiboperationen. Commands repräsentieren Absichten, die den Zustand des Systems ändern.

// src/Application/Command/CreateProduct.php

class CreateProduct
{
public function __construct(
public readonly string $id,
public readonly string $name,
public readonly int $priceInCents
) {}
}

Command Handler führen die entsprechenden Commands aus:

// src/Application/Command/Handler/CreateProductHandler.php

use App\Domain\Model\Product;
use App\Domain\ValueObject\Price;
use App\Domain\Repository\ProductRepository;

class CreateProductHandler
{
public function __construct(private ProductRepository $repository) {}

public function __invoke(CreateProduct $command): void
{
$product = new Product(
$command->id,
$command->name,
new Price($command->priceInCents)
);

$this->repository->save($product);
}
}

Query und Query Handler

Queries sind für Leseoperationen zuständig und ändern nicht den Zustand des Systems.

// src/Application/Query/GetProduct.php

class GetProduct
{
public function __construct(public readonly string $id) {}
}

Query Handler:

// src/Application/Query/Handler/GetProductHandler.php

use App\Domain\Repository\ProductRepository;
use App\Application\DTO\ProductDTO;

class GetProductHandler
{
public function __construct(private ProductRepository $repository) {}

public function __invoke(GetProduct $query): ?ProductDTO
{
$product = $this->repository->findById($query->id);

return $product ? new ProductDTO($product) : null;
}
}

Data Transfer Objects (DTOs)

DTOs sind einfache Datenobjekte, die zum Datentransport zwischen Schichten verwendet werden. Sie schützen die Domänenmodelle vor direkter Exposition.

// src/Application/DTO/ProductDTO.php

use App\Domain\Model\Product;

class ProductDTO
{
public string $id;
public string $name;
public int $priceInCents;

public function __construct(Product $product)
{
$this->id = $product->getId();
$this->name = $product->getName();
$this->priceInCents = $product->getPrice()->getAmount();
}
}

Anwendungsservices

Anwendungsservices orchestrieren komplexere Anwendungsfälle, die mehrere Domänenoperationen umfassen können.

// src/Application/Service/CheckoutService.php

use App\Domain\Model\ShoppingCart;
use App\Domain\Service\DiscountService;

class CheckoutService
{
public function __construct(private DiscountService $discountService) {}

public function checkout(ShoppingCart $cart): int
{
foreach ($cart->getItems() as $item) {
$this->discountService->applyDiscount($item, 10);
}

return $cart->getTotal();
}
}

Event Handler

Event Handler reagieren auf Domänen-Events und führen entsprechende Aktionen aus.

// src/Application/EventHandler/SendNotificationOnProductRenamed.php

use App\Domain\Event\ProductRenamed;

class SendNotificationOnProductRenamed
{
public function __invoke(ProductRenamed $event): void
{
// Beispiel: E-Mail oder Logik anstoßen
}
}

Validator

Validatoren prüfen die Eingabedaten, bevor sie an die Domäne weitergegeben werden.

// src/Application/Validator/CreateProductValidator.php

class CreateProductValidator
{
public function validate(array $input): void
{
if (!isset($input['name']) || trim($input['name']) === '') {
throw new \InvalidArgumentException('Name darf nicht leer sein.');
}

if (!is_numeric($input['price']) || $input['price'] < 0) {
throw new \InvalidArgumentException('Preis muss positiv sein.');
}
}
}

Command Bus und Query Bus

In größeren Anwendungen kann ein Command Bus oder Query Bus verwendet werden, um Commands und Queries an ihre Handler zu verteilen.

$command = new CreateProduct('123', 'Testprodukt', 999);
$commandBus->dispatch($command);

Integration mit Symfony Messenger

In Symfony-Anwendungen kann der Symfony Messenger als Implementierung für Command Bus und Event Dispatcher verwendet werden.

# config/packages/messenger.yaml
framework:
messenger:
routing:
'App\Application\Command\CreateProduct': sync

Zusammenfassung

Die Anwendungs-Schicht:

  1. orchestriert den Fluss von Daten zwischen der Domäne und der äußeren Welt
  2. implementiert Anwendungsfälle (Use Cases) durch Commands, Queries und Services
  3. transformiert Domänenmodelle in DTOs für die Präsentation
  4. validiert Eingabedaten, bevor sie an die Domäne weitergegeben werden
  5. reagiert auf Domänen-Events und führt entsprechende Aktionen aus

Die Anwendungs-Schicht sollte keine eigene Geschäftslogik enthalten, sondern nur die Orchestrierung der Domänenlogik. Sie ist der Einstiegspunkt für alle Interaktionen mit der Domäne und schützt sie vor direktem Zugriff von außen.