Pattern UseCase: Strukturování obchodní logiky v backendovém vývoji
UseCase je architektonický pattern pro popis obchodních scénářů v aplikacích. Pomáhá organizovat kód kolem konkrétních akcí systému, jako je vytvoření objednávky nebo registrace uživatele, místo abstraktních technických operací. Tento přístup zlepšuje čitelnost, zjednodušuje testování a škálování projektů, zejména ve složitých backendových systémech.
Co je UseCase a proč je potřeba
UseCase představuje ucelený obchodní scénář na aplikační vrstvě (Application Layer). Řídí sekvenci kroků ve scénáři, deleguje obchodní pravidla na doménové služby a entity. Například místo technické operace "zapsat řádek do databáze" UseCase popisuje akci "uživatel zadává objednávku". Tím se řeší problém rozptýlení logiky mezi kontroléry a službami, ke kterému dochází s růstem projektu. Bez jasných hranic scénářů se kód stává složitým pro pochopení a údržbu.
Klíčové výhody UseCase:
- Jasná struktura: Systém je popsán pomocí zjevných obchodních možností, nikoli abstraktních entit jako Service nebo Manager.
- Zlepšená čitelnost: Architektura je srozumitelná na úrovni obchodní logiky, což usnadňuje odpověď na otázku "Co umí aplikace dělat?".
- Snadné testování: Integrační testy na úrovni scénáře umožňují ověřit celou cestu uživatele bez rizika narušení obchodního procesu.
Omezení a doporučení pro použití
Ve většině případů se nedoporučuje volat jeden UseCase z jiného, aby se předešlo skrytým závislostem a přeměně UseCase na znovu použitelné bloky logiky. V složitých scénářích je to však přijatelné při absenci cyklických závislostí a zachování jasnosti scénářů. Pokud vznikne potřeba opakovaného použití logiky, měla by být vyjmuta do samostatných služeb nebo doménových komponent, které pak mohou být použity v různých UseCase.
Kde se volají UseCase:
- Controller
- Console Command
- Job
- Event Listener
UseCase slouží jako vstupní bod do obchodní logiky aplikace, přes který vnější svět interaguje se systémem.
Proč Service vrstva nemusí stačit
Na začátku projektu často stačí Service vrstva, ale s růstem obchodní logiky se objevují problémy. Například vytvoření objednávky z jednoduché operace uložení do databáze může získat další kroky: kontrola zásob, uplatnění slev, odečtení zůstatku, odeslání notifikací. To vede ke vzniku božských tříd (God-object) s mnoha závislostmi, metodami a vedlejšími efekty, což komplikuje čtení kódu a psaní testů.
UseCase nabízí jednoduché pravidlo: jeden UseCase = jeden obchodní proces. Opakovaná logika se vyjímá do samostatných služeb a doménových komponent, což zachovává řiditelnost kódu i při zvýšení složitosti.
Struktura UseCase na příkladu PHP a Laravel
Typická struktura UseCase zahrnuje tři komponenty:
- Handler: Řídí sekvenci provádění scénáře, obsahuje podmínky kroků, ale ne doménovou obchodní logiku.
- DataInput: Objekt vstupních dat bez logiky, pouze typizovaná data, ideálně neměnitelná.
- DataOutput: Objekt výsledku provádění, tvořící jasný kontrakt (např. DTO).
Příklad vytvoření objednávky:
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
);
}
}
Organizace a testování UseCase v projektech
V Laravel se doporučuje uchovávat UseCase ve struktuře, například app/UseCases/V1, s verzováním pro bezpečnou změnu kontraktů. Adresáře uvnitř odpovídají na otázku "Co umí systém dělat?", například StoreOrder, CancelOrder, RegisterUser. To odráží obchodní možnosti systému, nikoli technické detaily.
Testování UseCase se zjednodušuje díky integračním testům na úrovni scénáře. Příklad testu pro 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
]);
}
}
Co je důležité
- UseCase popisuje obchodní scénáře, nikoli technické operace, což zlepšuje strukturu aplikace.
- Pattern řeší problém rozptýlení logiky ve velkých projektech, čímž dělá kód více řiditelným.
- Jeden UseCase odpovídá jednomu obchodnímu procesu, s vyjmutím opakované logiky do samostatných služeb.
- Testování se zjednodušuje díky integračním testům, které ověřují celý scénář.
- Struktura projektu odráží obchodní možnosti, což urychluje zapojení nových vývojářů.
— Editorial Team
Zatím žádné komentáře.