Rehber: Laravel Architecture

# Rehber: Laravel Architecture Laravel, 2011'deki ilk sürümünden bu yana PHP ekosisteminin en çok tercih edilen framework'ü haline geldi. Ancak çoğu...

Rehber: Laravel Architecture

Rehber: Laravel Architecture

Laravel, 2011'deki ilk sürümünden bu yana PHP ekosisteminin en çok tercih edilen framework'ü haline geldi. Ancak çoğu geliştirici Laravel'i öğrenirken rotalar, controller'lar ve Eloquent modelleriyle sınırlı kalıyor; framework'ün altındaki mimari katmanları çoğu zaman göz ardı ediyor. Bu rehber, Laravel'in mimarisini yüzeysel bir "nasıl kullanılır" anlatımından öteye taşıyıp, uygulamanızı ölçeklenebilir, test edilebilir ve sürdürülebilir kılan yapısal kararlara odaklanıyor.

İster yeni bir projeye başlıyor olun, ister mevcut bir monoliti toparlamaya çalışıyor olun; bu yazıda Laravel'in servis container'ından request lifecycle'ına, katmanlı mimariden domain-driven yaklaşımlara kadar geniş bir harita çizeceğiz.

Laravel'in Temel Felsefesi: "Convention over Configuration"

Laravel'in mimarisini anlamak için önce onu şekillendiren felsefeyi kavramak gerekir. Taylor Otwell'in tasarım tercihleri üç eksende toplanır:

  • Geliştirici deneyimi önce gelir: Karmaşık yapılandırma yerine mantıklı varsayılanlar.
  • İfade gücü: Az kod ile çok iş yapmak; "expressive syntax" felsefesi.
  • Modern PHP pratikleri: PSR standartları, Composer, dependency injection gibi endüstri normlarını benimsemek.

Bu felsefe, framework'ün "görünmez mimarisi"nin neden bu kadar akıcı olduğunu açıklar. Ancak bu akıcılık, projeler büyüdükçe bir tuzağa dönüşebilir: her şey controller içinde yazılabilir hale gelir ve mimari disiplin kaybolur.

Laravel Uygulamasının Katmanları

Klasik bir Laravel uygulaması, birbirine gevşek bağlı (loosely coupled) katmanlardan oluşur. Bu katmanları tanımak, sağlıklı bir mimari kurmanın ilk adımıdır.

1. HTTP Katmanı

Rotalar (routes/), middleware'ler ve controller'lar bu katmanda yaşar. Bu katmanın tek sorumluluğu olmalıdır: istekleri almak ve yanıt üretmek. İş mantığının buraya sızması, en yaygın Laravel antipattern'idir.

2. Uygulama Katmanı (Application Layer)

Servisler, action'lar ve use-case sınıfları burada bulunur. "Kullanıcı kaydolduğunda ne olacak?" sorusunun cevabı bu katmanda yaşar.

3. Domain Katmanı

İş kuralları, entity'ler, value object'ler ve domain servisleri. Bu katman framework'ten mümkün olduğunca bağımsız olmalıdır; taşınabilirliği ve test edilebilirliği artırır.

4. Altyapı Katmanı (Infrastructure)

Eloquent modelleri, repository implementasyonları, harici API istemcileri, mail servisleri. Framework'e en bağımlı katman burasıdır.

Bu katmanlı bakış açısı, Laravel'in varsayılan klasör yapısıyla birebir örtüşmez. Varsayılan yapı küçük projeler için mükemmeldir; ancak orta ve büyük ölçekli projelerde bu katmanları kendi klasör yapınızla modellemeniz gerekir.

Servis Container ve Dependency Injection

Laravel'in kalbindeki en güçlü mekanizma, Service Container'dır. Neredeyse tüm framework bileşenleri bu container üzerinden çözümlenir.

// Basit bir binding
$this->app->bind(PaymentGateway::class, StripeGateway::class);

// Singleton binding
$this->app->singleton(ReportGenerator::class, function ($app) {
    return new ReportGenerator($app->make(CacheStore::class));
});

Servis container'ın gerçek gücü, otomatik çözümleme (auto-wiring) yeteneğinde yatar. Bir controller'a bağımlılık enjekte ettiğinizde Laravel, bağımlılık ağacını otomatik olarak çözer:

class OrderController extends Controller
{
    public function __construct(
        private OrderService $orders,
        private PaymentGateway $gateway,
    ) {}
}

Bu mekanizmayı doğru kullanmak için birkaç pratik kural:

  • Interface'e bağlanın, concrete class'a değil. bind(PaymentGateway::class, StripeGateway::class) gibi.
  • Servis provider'ları modüler tutun. Her domain modülü kendi provider'ına sahip olsun.
  • Contextual binding'den faydalanın. Aynı interface'in farklı sınıflar için farklı implementasyonlarını bağlayabilirsiniz.

Request Lifecycle: Bir İsteğin Yolculuğu

Laravel'de bir HTTP isteğinin izlediği yol, mimariyi anlamanın en somut yoludur:

  1. public/index.php — Tüm isteklerin giriş noktası. Composer autoloader yüklenir ve application instance'ı oluşturulur.
  2. HTTP Kernel — Global middleware'ler (session, CORS, CSRF) burada çalışır.
  3. Router — İstek, tanımlı rotalarla eşleştirilir.
  4. Route Middleware — Rota bazlı middleware'ler (auth, throttle) devreye girer.
  5. Controller / Closure — İstek işlenir, servis container üzerinden bağımlılıklar çözülür.
  6. Response — Yanıt oluşturulur ve middleware zincirinden geri döner.
  7. Terminate — terminate metodu çağrılır, ardından yanıt kullanıcıya gönderilir.

Bu akışı bilmek, performans darboğazlarını ve mimari hataları teşhis etmede kritik önem taşır. Örneğin, bir middleware içinde ağır bir veritabanı sorgusu çalıştırmak her isteği yavaşlatır; bu tür işler genellikle event listener'lara veya queue job'larına taşınmalıdır.

Eloquent ve Repository Pattern İkilemi

Laravel topluluğunda en çok tartışılan konulardan biri: Eloquent'i doğrudan mı kullanmalı, yoksa Repository pattern ile mi sarmalamalı?

Doğrudan Eloquent Kullanımı

Avantajlar:

  • Daha az kod, daha hızlı geliştirme.
  • Laravel'in tüm gücünden (scope'lar, ilişkiler, eager loading) yararlanma.
  • Framework'ün doğal akışına uyum.

Dezavantajlar:

  • Controller'lar zamanla "şişer" (fat controller).
  • Test etmek için veritabanı gerekir.
  • Domain mantığı veri erişimiyle karışır.

Repository Pattern

Avantajlar:

  • Veri erişimi soyutlanır, test edilebilirlik artar.
  • Domain katmanı framework'ten izole olur.
  • Farklı veri kaynaklarına geçiş kolaylaşır.

Dezavantajlar:

  • Ekstra soyutlama katmanı, ekstra kod.
  • Eloquent'in güçlü özelliklerinden feragat.
  • Yanlış uygulandığında "anemic" bir yapıya dönüşür.

Pratik öneri: Küçük ve orta ölçekli projelerde doğrudan Eloquent kullanmak genellikle daha verimlidir. Ancak domain karmaşıklığı arttığında, özellikle birden fazla veri kaynağı veya dış servis entegrasyonu varsa, repository pattern'e geçmek mantıklıdır. Her projeye aynı şablonu dayatmayın.

Action Sınıfları ve Servis Katmanı

Fat controller sorununa en temiz çözümlerden biri, iş mantığını action sınıflarına taşımaktır. Her action tek bir işi yapar:

class RegisterUser
{
    public function __construct(
        private UserRepository $users,
        private Mailer $mailer,
    ) {}

    public function handle(RegisterUserData $data): User
    {
        $user = $this->users->create($data->toArray());
        $this->mailer->sendWelcome($user);

        return $user;
    }
}

Controller ise yalnızca HTTP sorumluluğunu taşır:

class RegisterController extends Controller
{
    public function __invoke(RegisterRequest $request, RegisterUser $action)
    {
        $user = $action->handle(RegisterUserData::from($request));

        return UserResource::make($user);
    }
}

Bu yaklaşımın faydaları:

  • Tek sorumluluk: Her sınıf tek bir işi yapar.
  • Test edilebilirlik: Action'lar HTTP'den bağımsız test edilebilir.
  • Yeniden kullanılabilirlik: Aynı action hem web hem API hem de command'da kullanılabilir.
  • Okunabilirlik: Controller'lar "ne yapıldığını" anlatır, "nasıl yapıldığını" değil.

Domain-Driven Design ve Laravel

Laravel, DDD'ye zorlamaz; ancak DDD prensiplerini uygulamak için gerekli altyapıyı sunar. Orta ve büyük projelerde düşünülebilecek bir klasör yapısı:

app/
├── Domain/
│   ├── Order/
│   │   ├── Models/
│   │   ├── ValueObjects/
│   │   ├── Services/
│   │   └── Events/
│   └── User/
├── Application/
│   ├── Order/
│   │   ├── Actions/
│   │   └── DTOs/
│   └── User/
├── Infrastructure/
│   ├── Persistence/
│   └── External/
└── Http/
    ├── Controllers/
    ├── Requests/
    └── Resources/

Bu yapı, Laravel'in varsayılanından sapar ama şu avantajları getirir:

  • Modülerlik: Her domain kendi içinde tutarlıdır.
  • Bağımlılık yönü: Domain, altyapıya değil; altyapı, domain'e bağımlıdır.
  • Ekip ölçeklenmesi: Farklı ekipler farklı domain'lerde çalışabilir.

Ancak dikkat: DDD bir araçtır, amaç değil. Karmaşık iş kuralları olmayan projelerde zorlama DDD, gereksiz soyutlama yaratır.

Event-Driven Yaklaşım

Laravel'in event sistemi, mimariyi gevşek bağlı hale getirmenin en zarif yollarından biridir. Bir kullanıcı kaydolduğunda yapılması gereken birden fazla iş varsa (hoş geldin maili, CRM'e kayıt, analytics event'i), bunları event listener'lara dağıtabilirsiniz:

event(new UserRegistered($user));

Bu yaklaşımın faydaları:

  • Ayrıştırma: Kayıt işlemi, yan etkilerden haberdar olmak zorunda değil.
  • Genişletilebilirlik: Yeni bir yan etki eklemek için mevcut kodu değiştirmek gerekmez.
  • Asenkron çalışma: Listener'ları queue'ya atayarak yanıt sürelerini düşürebilirsiniz.

Ancak aşırıya kaçmayın: Her işlemi event'e dönüştürmek, kodun akışını takip etmeyi zorlaştırır. Kritik akışlar için doğrudan çağrı, yan etkiler için event kullanmak dengeli bir yaklaşımdır.

Test Edilebilir Mimari

İyi mimarinin en somut göstergesi, test edilebilirliğidir. Laravel'de test piramidini şöyle kurgulamak faydalıdır:

  • Unit testler: Domain mantığı, value object'ler, action'lar. Veritabanı gerektirmez, hızlıdır.
  • Feature testler: HTTP endpoint'leri, controller'lar. RefreshDatabase ile izole edilir.
  • Integration testler: Harici servis entegrasyonları, queue işleri.

Test edilebilirliği artıran mimari kararlar:

  • Interface'lere bağımlılık: Mock'lamayı kolaylaştırır.
  • Yan etkileri izole etmek: Mail, queue, HTTP client gibi bağımlılıklar inject edilmeli.
  • Framework'ten bağımsız domain: Domain sınıfları app() veya config() gibi global helper'lara bağımlı olmamalı.

Sık Yapılan Mimari Hatalar

Deneyimlerime göre Laravel projelerinde en sık karşılaşılan mimari hatalar şunlardır:

  1. Fat controller sendromu: İş mantığının doğrudan controller'a yazılması.
  2. Fat model sendromu: Eloquent modellerinin hem veri erişimi hem iş mantığı hem de sunum sorumluluğu üstlenmesi.
  3. Service container'ı görmezden gelmek: new ile sınıf oluşturmak; test edilebilirliği yok eder.
  4. Aşırı soyutlama: Kullanılmayan interface'ler, gereksiz repository'ler, "belki lazım olur" sınıfları.
  5. Event'leri suistimal etmek: Kritik iş akışlarını event'lere bölüp akışı takip edilemez hale getirmek.
  6. Queue'yu geç keşfetmek: Uzun süren işleri senkron çalıştırıp kullanıcıyı bekletmek.
  7. Migration disiplinsizliği: Üretimde manuel veritabanı değişikliği yapmak.

Sonuç: Mimari Bir Yolculuktur

Laravel'in mimarisi, size hazır bir "en iyi pratikler" seti sunmaz; size araçlar sunar. Bu araçları nasıl kullanacağınız, projenizin büyüklüğüne, ekibinizin deneyimine ve domain'inizin karmaşıklığına bağlıdır.

Unutmayın: En iyi mimari, ihtiyacınıza en uygun olandır. Küçük bir MVP için katmanlı mimari kurmak zaman kaybıdır; büyük bir kurumsal uygulamayı tek controller içinde yazmak ise teknik borç yaratır. Bu dengeyi bulmak, deneyimle ve sürekli öğrenmeyle gelir.

Laravel ekosistemi sürekli evriliyor: Laravel Octane ile performans, Laravel Pennant ile feature flag'ler, Livewire ile frontend bütünleşmesi. Mimari kararlarınızı verirken bu ekosistemi bir bütün olarak değerlendirin ve framework'ün sizin için çalışmasını sağlayın; siz framework için değil.

İLGİLİ İÇERİKLER

Bunları da Okumak İsteyebilirsiniz