Bir CreateOrderController yazdınız. Ardından ürün ekibi bir CSV ithalatçısı istedi, böylece ImportOrdersCommand yazdınız ve kontrolcü mantığını handle() metoduna yapıştırdınız. Daha sonra satış ekibi, admin panelde asenkron çalışan bir “sipariş ver” butonu istedi, bu yüzden CreateOrderJob yazdınız ve aynı mantığı üçüncü kez yapıştırdınız.
<p>Aynı doğrulamanın üç kopyası. Aynı <code>DB::transaction</code> sarmalayıcısının üç kopyası. "Stokta ürün yoksa ne olur" sorusunun üç kopyası. HTTP yolundaki bug raporları geliyor. Orayı düzeltiyorsunuz. İki hafta sonra, aynı bug CSV ithalatçısından tekrar yüzeye çıkıyor çünkü kimse başka iki kopyanın varlığını hatırlamıyor.</p>
<p>Bu durum, hexagonal mimarinin çözmek için var olduğu bir kokudur. Bir özellik değil, temel bir kontrolcü değil. Çözüm, nasıl çağrıldığını bilmeyen tek bir sınıf ve girişi istediği forma çeviren üç ince adaptörle ön yüz tasarımıdır.</p>
<p>Bunu herhangi bir framework ile yapabilirsiniz. Örnek Laravel içindir çünkü en yaygın kullanımdır, ancak bu desen Symfony veya Slim portunda değişmeden devam eder.</p>
<h2>
<a name="the-use-case-is-just-a-class" href="#the-use-case-is-just-a-class"></a>
Kullanım durumu sadece bir sınıftır
</h2>
<p>Bir kullanım durumu, düzenli bir PHP sınıfıdır. Bir public metod. Bir input DTO. Bir output DTO. Hiçbir <code>Request</code>, hiçbir <code>Command</code>, hiçbir <code>Job</code> yok. Ne tarafından çağrıldığını bilmez ve umursamaz.</p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><?phpdeclare(strict_types=1);
namespace App\Domain\Order\UseCase;
use App\Domain\Order\Entity\Order;
use App\Domain\Order\Exception\InsufficientStock;
use App\Domain\Order\Port\OrderRepository;
use App\Domain\Order\Port\InventoryChecker;
use App\Domain\Order\Port\Clock;
final class CreateOrderUseCase
{
public function __construct(
private readonly OrderRepository $orders,
private readonly InventoryChecker $inventory,
private readonly Clock $clock,
) {}
public function execute(CreateOrderInput $input): CreateOrderOutput
{
foreach ($input->items as $item) {
if (!$this->inventory->hasStock($item->sku, $item->quantity)) {
throw new InsufficientStock($item->sku);
}
}
$order = Order::place(
customerId: $input->customerId,
items: $input->items,
placedAt: $this->clock->now(),
);
$this->orders->save($order);
return new CreateOrderOutput(
orderId: $order->id(),
totalCents: $order->totalCents(),
placedAt: $order->placedAt(),
);
}}
<p>Input ve output DTO'ları, basit readonly nesnelerdir. Hiçbir framework tip ipuçları yoktur.</p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><?phpdeclare(strict_types=1);
namespace App\Domain\Order\UseCase;
use App\Domain\Order\ValueObject\OrderItem;
final readonly class CreateOrderInput
{
/* @param OrderItem[] $items /
public function __construct(
public string $customerId,
public array $items,
public ?string $idempotencyKey = null,
) {}
}
final readonly class CreateOrderOutput
{
public function __construct(
public string $orderId,
public int $totalCents,
public \DateTimeImmutable $placedAt,
) {}
}


