UseCase-Pattern: Geschäftslogik im Backend strukturieren
Das UseCase-Pattern ist ein Architekturansatz, um Geschäftszenarien in Anwendungen klar zu definieren. Es organisiert den Code um konkrete Systemaktionen – wie das Erstellen einer Bestellung oder die Benutzerregistrierung – statt abstrakter technischer Operationen. Dieser Ansatz verbessert die Lesbarkeit, vereinfacht Tests und skaliert Projekte effektiv, besonders in komplexen Backend-Systemen.
Was ist UseCase und wofür lohnt es sich?
Ein UseCase stellt ein vollständiges Geschäftszenario in der Anwendungsschicht dar. Er steuert eine Abfolge von Schritten und delegiert Geschäftsregeln an Domänen-Services und Entities. Statt einer niedrigstufigen „Datenbank schreiben“-Operation beschreibt ein UseCase „Benutzer erstellt Bestellung“. So verhindert man, dass sich Geschäftslogik bei wachsendem Projekt über Controller und Services ausbreitet. Ohne klare Szenario-Grenzen wird der Code schwer verständlich und wartbar.
Wichtige Vorteile des UseCase-Patterns:
- Klare Struktur: Das System wird durch explizite Geschäftsfunktionen definiert, nicht durch vage Abstraktionen wie Service oder Manager.
- Bessere Lesbarkeit: Die Architektur spiegelt die Geschäftslogik wider und beantwortet leicht „Was kann diese App?“.
- Einfachere Tests: Integrationstests auf Szenariosebene prüfen den gesamten Nutzerflow, ohne Geschäftsprozesse zu zerlegen.
Einschränkungen und Best Practices
Vermeiden Sie Aufrufe eines UseCases aus einem anderen, um versteckte Abhängigkeiten zu verhindern und sie nicht zu wiederverwendbaren Logikblöcken zu machen. In komplexen Szenarien sind zyklische Abhängigkeiten okay, solange Szenarien explizit bleiben. Für wiederverwendbare Logik extrahieren Sie diese in dedizierte Services oder Domänen-Komponenten, die in mehreren UseCases genutzt werden können.
Wo UseCases aufrufen:
- Controller
- Console Command
- Job
- Event Listener
UseCase dient als Einstiegspunkt in die Geschäftslogik Ihrer App und vermittelt externe Interaktionen mit dem System.
Warum Services allein nicht reichen
Services funktionieren bei einfachen Projekten gut, aber bei wachsender Geschäftslogik treten Probleme auf. Was als einfaches „in Datenbank speichern“ für Bestellungen beginnt, wächst zu Prüfungen auf Lagerbestand, Rabattanwendung, Kontostornierung und Benachrichtigungen. Das führt zu God-Objects – Klassen voller Abhängigkeiten, Methoden und Nebenwirkungen –, die den Code unlesbar und schwer testbar machen.
UseCase folgt einer einfachen Regel: Ein UseCase = ein Geschäftsprozess. Duplizierte Logik wandert in geteilte Services oder Domänen-Komponenten, um den Code bei steigender Komplexität handhabbar zu halten.
UseCase-Struktur in PHP und Laravel
Ein typischer UseCase besteht aus drei Teilen:
- Handler: Steuert Szenario-Schritte und Bedingungen, ohne eigene Domänen-Geschäftslogik.
- DataInput: Eingabe-Datenobjekt ohne Logik – nur typisierte, vorzugsweise unveränderliche Daten.
- DataOutput: Ergebnis-Objekt mit klarem Vertrag (z. B. DTO).
Beispiel: Bestellung erstellen:
class StoreOrderDataInput
{
public function __construct(
public readonly int $userId,
public readonly array $items,
) {}
}
class StoreOrderDataOutput
{
public function __construct(
public readonly int $orderId,
public readonly string $status,
) {}
}
class StoreOrderHandler
{
public function __construct(
private OrderRepository $orders
) {}
public function handle(StoreOrderDataInput $input): StoreOrderDataOutput
{
$order = new Order(
userId: $input->userId,
);
$orderSaved = $this->orders->save($order);
return new StoreOrderDataOutput(
orderId: $orderSaved->id,
status: $orderSaved->status
);
}
}
UseCases in Projekten organisieren und testen
In Laravel speichern Sie UseCases in einer Struktur wie app/UseCases/V1 mit Versionierung für sichere Vertragsänderungen. Unterverzeichnisse beantworten „Was kann das System?“ – z. B. StoreOrder, CancelOrder, RegisterUser. Das spiegelt Geschäftsfunktionen wider, nicht technische Details.
Das Testen von UseCases gelingt unkompliziert mit Integrationstests auf Szenariosebene. Beispieltest für StoreOrderHandler:
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class StoreOrderFeatureTest extends TestCase
{
use RefreshDatabase;
public function testUserCanCreateOrder(): void
{
$user = User::factory()->create();
$payload = [
'items' => [
['id' => 10, 'qty' => 2],
['id' => 15, 'qty' => 1],
],
];
$response = $this->actingAs($user)
->postJson('/api/orders', $payload);
$response->assertStatus(200);
$response->assertJsonStructure([
'orderId',
'status'
]);
$this->assertDatabaseHas('orders', [
'user_id' => $user->id
]);
}
}
Wichtige Erkenntnisse
- UseCase definiert Geschäftszenarien, nicht technische Operationen, für eine saubere App-Struktur.
- Es löst Logik-Verwucherung in großen Projekten und hält Code wartbar.
- Ein UseCase = ein Geschäftsprozess, mit geteilter Logik in Services.
- Tests werden einfacher durch Integrationstests für komplette Szenarien.
- Projektstruktur spiegelt Geschäftsfeatures, beschleunigt Einarbeitung.
— Editorial Team
Noch keine Kommentare.