· Admin

Domain-Driven Design (DDD) ile Laravel Projeleri Yapılandırma

Laravel hızlı geliştirme için harika ama proje büyüdükçe klasik MVC tek başına yetmemeye başlıyor. İş kuralları controller'lara dağılıyor, model'ler şişiyor, kod okunamaz hale geliyor. DDD yaklaşımı tam bu noktada devreye giriyor.

DDD Nedir?

Domain-Driven Design, yazılım mimarisini iş kuralları (business rules) etrafında şekillendiren bir yaklaşım. Teknik detaylardan önce iş alanına (domain) odaklanmayı ve mimariyi bu doğrultuda tasarlamayı amaçlar.

Temel prensipleri:

  • İş kuralları merkezde yer alır
  • Teknik detaylar ikinci plandadır
  • Kod, iş dünyasındaki kavramlarla birebir örtüşür
  • Domain, uygulamanın kalbidir

Neden DDD?

Laravel varsayılan olarak hızlı geliştirme odaklı. Küçük projelerde MVC yeterli. Ama şu senaryolarda DDD ciddi avantaj sağlar:

  • Büyük ekipler ve uzun ömürlü projeler
  • Karmaşık iş kuralları olan uygulamalar
  • SaaS ve enterprise sistemler
  • Bakım maliyetinin düşük tutulması gereken projeler

Temel Kavramlar

Entity

Entity, kimliği olan ve yaşam döngüsü bulunan domain nesnesi:

class User
{
    public function __construct(
        private UserId $id,
        private Email $email,
        private string $name
    ) {}

    public function id(): UserId
    {
        return $this->id;
    }

    public function changeEmail(Email $newEmail): void
    {
        $this->email = $newEmail;
    }
}

Value Object

Value Object'ler kimliksizdir, değerleriyle anlam kazanır ve immutable yapıdadır:

class Email
{
    public function __construct(private string $value)
    {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Geçersiz email');
        }
    }

    public function value(): string
    {
        return $this->value;
    }

    public function equals(Email $other): bool
    {
        return $this->value === $other->value();
    }
}

Value Object'in güzelliği: email validasyonu bir kez yazılır, her yerde tutarlı çalışır. string $email yerine Email $email kullandığınızda geçersiz email zaten sisteme giremez.

Aggregate ve Aggregate Root

Aggregate, birlikte tutarlı kalması gereken entity'ler bütünü. Aggregate Root ise bu bütünün dış dünyaya açılan tek kapısı:

class Order // Aggregate Root
{
    private array $items = [];
    private OrderStatus $status;

    public function __construct(
        private OrderId $id,
        private CustomerId $customerId
    ) {
        $this->status = OrderStatus::PENDING;
    }

    public function addItem(Product $product, int $quantity): void
    {
        if ($this->status !== OrderStatus::PENDING) {
            throw new DomainException('Onaylanmış siparişe ürün eklenemez.');
        }

        $this->items[] = new OrderItem($product, $quantity);
    }

    public function confirm(): void
    {
        if (empty($this->items)) {
            throw new DomainException('Boş sipariş onaylanamaz.');
        }

        $this->status = OrderStatus::CONFIRMED;
    }
}

Sipariş kuralları (Order içine ürün ekleme, onaylama) burada tanımlanıyor — controller'da değil.

Repository Pattern

Repository, domain katmanının veri kaynağıyla doğrudan temas etmesini engeller:

interface OrderRepository
{
    public function save(Order $order): void;
    public function findById(OrderId $id): ?Order;
    public function findByCustomer(CustomerId $customerId): array;
}

class EloquentOrderRepository implements OrderRepository
{
    public function save(Order $order): void
    {
        // Eloquent ile kaydetme implementasyonu
        $model = OrderModel::updateOrCreate(
            ['id' => $order->id()->value()],
            $order->toArray()
        );
    }

    public function findById(OrderId $id): ?Order
    {
        $model = OrderModel::find($id->value());
        return $model ? $this->toDomain($model) : null;
    }
}

Klasör Yapısı

Domain Katmanı

app/
 └── Domains/
     └── Order/
         ├── Entities/
         │   ├── Order.php
         │   └── OrderItem.php
         ├── ValueObjects/
         │   ├── OrderId.php
         │   └── OrderStatus.php
         ├── Repositories/
         │   └── OrderRepository.php  (interface)
         ├── Services/
         │   └── OrderPricingService.php
         └── Events/
             └── OrderConfirmed.php

Application Katmanı (Use Case'ler)

app/
 └── Application/
     └── Order/
         ├── CreateOrder.php
         ├── ConfirmOrder.php
         └── CancelOrder.php

Infrastructure Katmanı

app/
 └── Infrastructure/
     └── Persistence/
         └── Eloquent/
             └── EloquentOrderRepository.php

Örnek Use Case

class CreateOrder
{
    public function __construct(
        private OrderRepository $orders
    ) {}

    public function execute(CreateOrderRequest $request): Order
    {
        $order = new Order(
            new OrderId(Str::uuid()),
            new CustomerId($request->customerId)
        );

        foreach ($request->items as $item) {
            $order->addItem($item['product'], $item['quantity']);
        }

        $this->orders->save($order);

        return $order;
    }
}

Controller Ne İş Yapar?

Controller DDD'de sadece giriş noktası. İş mantığı yok:

class OrderController extends Controller
{
    public function store(Request $request, CreateOrder $useCase)
    {
        $order = $useCase->execute(
            new CreateOrderRequest($request->validated())
        );

        return response()->json([
            'id' => $order->id()->value(),
            'status' => 'created',
        ], 201);
    }
}

Service Container Binding

// AppServiceProvider
public function register(): void
{
    $this->app->bind(
        OrderRepository::class,
        EloquentOrderRepository::class
    );
}

DDD Her Proje İçin Uygun mu?

Kesinlikle hayır.

Uygun olan projeler:

  • Büyük ölçekli uygulamalar, karmaşık iş kuralları
  • Uzun ömürlü projeler, SaaS ve enterprise sistemler
  • Birden fazla geliştirici, bakım önemli

Uygun olmayan projeler:

  • Küçük CRUD projeleri, MVP aşamasındaki ürünler
  • Kısa vadeli uygulamalar
  • Tek geliştiriciyle basit projeler

Sonuç

DDD, başlangıçta ek efor gerektirir ama uzun vadede kazandırdığı bakım kolaylığı, test edilebilirlik ve modülerlik buna değer. Önemli olan pragmatik olmak — her projede DDD şart değil, ama ihtiyaç olduğunda nasıl uygulanacağını bilmek büyük avantaj.