aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Geliştirme

Laravel Repository Pattern: Gerekli mi, Aşırı mı?

Laravel repository pattern, veri erişim mantığını controller ve servislerden ayırıp ayrı bir katmana taşıyan klasik bir tasarım desenidir. Fikir basit: kodunuz veriyi nereden çektiğini (Eloquent, ham sorgu, harici API) bilmesin; yalnızca bir UserRepository arayüzüne find() ya da all() der ve sonucu alır. Kulağa temiz geliyor, ama Laravel topluluğunda en çok tartışılan konulardan biridir: çünkü Eloquent zaten bir soyutlama katmanıdır ve üzerine ikinci bir katman koymak çoğu projede fayda yerine yük getirir. Bu yazıda deseni doğru kurmayı, gerçek artılarını, gizli maliyetlerini ve ne zaman aşırıya kaçtığını netleştiriyorum.

Repository pattern tam olarak ne çözer?

Desenin kökeni Domain-Driven Design'dır. Amaç, iş mantığını (domain) kalıcılık (persistence) ayrıntılarından izole etmektir. Teoride bir repository, bellekteki bir nesne koleksiyonu gibi davranır: ona nesne sorar, nesne verirsin; arkada SQL mi, dosya mı, bir REST API mi olduğu seni ilgilendirmez. Pratikte Laravel'de bu üç ana hedefe indirgenir:

  • Soyutlama: İş mantığı somut bir ORM'e değil, bir arayüze bağlanır.
  • Test edilebilirlik: Arayüzü sahteyle (mock) değiştirip veritabanına dokunmadan test yazabilirsiniz.
  • Tekrar kullanım: Karmaşık sorgular tek bir yerde toplanır, controller'lara dağılmaz.

Burada kritik soru şu: Eloquent zaten bir Active Record uygulamasıyla bu soyutlamanın bir kısmını sunmuyor mu? Evet — ve repository pattern'in en çok eleştirilen yanı tam olarak bu örtüşmedir.

Laravel'de doğru kurulum

Deseni uygulayacaksanız doğru yapın. Temel yapı bir arayüz ve onu uygulayan bir somut sınıftan oluşur. Önce arayüz:

namespace App\Repositories\Contracts;

interface UserRepositoryInterface
{
    public function all();
    public function find(int $id);
    public function create(array $data);
}

Ardından Eloquent tabanlı somut uygulama:

namespace App\Repositories\Eloquent;

use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;

class EloquentUserRepository implements UserRepositoryInterface
{
    public function __construct(private User $model) {}

    public function all()
    {
        return $this->model->all();
    }

    public function find(int $id)
    {
        return $this->model->findOrFail($id);
    }

    public function create(array $data)
    {
        return $this->model->create($data);
    }
}

Son adım, arayüzü somut sınıfa bağlamaktır. Bunu bir service provider'da yaparsınız:

// AppServiceProvider veya özel bir RepositoryServiceProvider içinde
public function register(): void
{
    $this->app->bind(
        \App\Repositories\Contracts\UserRepositoryInterface::class,
        \App\Repositories\Eloquent\EloquentUserRepository::class
    );
}

Artık controller arayüzü ister, Laravel servis konteyneri doğru uygulamayı enjekte eder:

public function show(UserRepositoryInterface $users, int $id)
{
    return view('users.show', [
        'user' => $users->find($id),
    ]);
}

Dikkat edin: bağlanma arayüze yapıldığı için, yarın Eloquent yerine başka bir uygulamaya geçmek istediğinizde yalnızca bind satırını değiştirirsiniz. Kâğıt üzerinde desenin vaadi budur.

Gerçek artıları

Desen, doğru bağlamda gerçekten değerlidir. En somut faydaları şunlardır:

  • Karmaşık sorguların tek noktada toplanması. Aynı çok aşamalı raporlama sorgusu beş farklı controller'da tekrarlanıyorsa, onu ReportRepository içinde topSpendingCustomers() gibi anlamlı bir metoda taşımak okunabilirliği ciddi artırır.
  • Birden fazla veri kaynağı. Veri kısmen MySQL'den, kısmen bir harici API'den geliyorsa, repository bu ikisini tek bir arayüz arkasında birleştirmek için doğal bir yerdir.
  • İş mantığını ORM'den koruma. Domain katmanınız büyük ve uzun ömürlüyse, onu Eloquent'in özelliklerine sıkı sıkıya bağlamamak gelecekteki bir ORM göçünü teorik olarak kolaylaştırır.

Dikkat ederseniz bu maddelerin ortak yanı, projenin belirli bir karmaşıklık eşiğini aşmış olmasıdır. Küçük ve orta projelerde bu koşullar nadiren oluşur.

Gizli maliyetler ve eleştiriler

Topluluğun deseni sık eleştirmesinin nedenleri var ve bunları görmezden gelmek hata olur:

  • Eloquent zaten soyutlamadır. User::find($id) de bir soyutlamadır; veritabanı sürücüsünü değiştirebilir, sorguyu siz görmeden üretir. Üzerine bir katman daha koymak çoğu zaman aynı şeyi iki kez yapmaktır.
  • Eloquent'in gücünü kaybedersiniz. Repository'niz koleksiyon döndürdüğünde, çağıran kod artık where(), with(), paginate() gibi akıcı zinciri kullanamaz. Bunları desteklemek için repository'ye onlarca metot eklersiniz ve sonunda Eloquent'i yeniden icat etmiş olursunuz.
  • "Sürücü değiştirme" senaryosu neredeyse hiç gerçekleşmez. Desenin baş gerekçesi "yarın Eloquent'ten ayrılırsak" varsayımıdır. Pratikte projelerin ezici çoğunluğu ömrü boyunca Eloquent'te kalır; olmayan bir soruna mühendislik yaparsınız.
  • Daha fazla dosya, daha fazla yük. Her model için arayüz, somut sınıf ve bağlama satırı; ekibe katılan yeni biri için fazladan dolaşılması gereken bir labirent.

Test edilebilirlik gerçekten gerekçe mi?

Repository savunucularının en güçlü kozu testtir: arayüzü mock'layıp veritabanına gitmeden test edersiniz. Bu doğru, ama Laravel'de alternatif maliyetsiz. RefreshDatabase trait'i ile hızlı bir SQLite bellek-içi veritabanı kullanmak çoğu test için yeterince hızlıdır ve gerçek sorgularınızı test eder — mock'lanmış bir arayüz, sorgu mantığınızda hata olsa bile yeşil kalır.

use Illuminate\Foundation\Testing\RefreshDatabase;

class UserTest extends TestCase
{
    use RefreshDatabase;

    public function test_kullanici_olusturulur(): void
    {
        $user = User::factory()->create(['email' => 'a@b.com']);
        $this->assertDatabaseHas('users', ['email' => 'a@b.com']);
    }
}

Yani "test için repository şart" iddiası, Laravel'in araçlarıyla çoğu durumda çürür. Test edilebilirlik repository'nin değil, iyi yapılandırılmış kodun bir sonucudur.

Pratik alternatif: ince servis katmanı veya Action sınıfları

Çoğu durumda istediğiniz şey aslında "controller'ları ince tutmak" ve "iş mantığını bir yere toplamak"tır — bu, tam repository soyutlaması gerektirmez. İki hafif yaklaşım yeterli olur. Servis sınıfları ilgili iş mantığını gruplar ama içinde rahatça Eloquent kullanır. Action sınıfları ise tek bir işi yapan, tek handle() metotlu küçük sınıflardır:

class CreateUser
{
    public function handle(array $data): User
    {
        $user = User::create($data);
        $user->sendWelcomeNotification();
        return $user;
    }
}

Bu yaklaşım kodu düzenler, Eloquent'in tüm gücünü korur ve gereksiz arayüz katmanından kaçınır. Pek çok deneyimli Laravel geliştiricisinin tam repository yerine tercih ettiği yol budur.

Sık Sorulan Sorular

Repository pattern'i her Laravel projesinde kullanmalı mıyım?

Hayır. Küçük ve orta ölçekli projelerin büyük çoğunluğunda gereksiz bir soyutlamadır; Eloquent'i doğrudan, gerekirse ince bir servis veya Action katmanıyla kullanmak daha sade ve bakımı kolaydır. Deseni ancak gerçekten birden fazla veri kaynağınız, ağır ve tekrarlayan sorgularınız veya ORM'den bağımsız büyük bir domain katmanınız varsa düşünün.

Repository pattern performansı etkiler mi?

Doğrudan ölçülebilir bir performans etkisi yoktur; ekstra sınıf çağrısının maliyeti ihmal edilebilir. Asıl risk dolaylıdır: koleksiyon döndüren naif bir repository, çağıran kodu with() ile eager loading yapmaktan alıkoyarsa N+1 sorgu problemine yol açabilir. Bu desenin değil, kötü uygulamanın sonucudur.

Repository pattern Eloquent'in yerini mi alır?

Hayır, onun yerine değil üzerine geçer. Repository içinde genellikle yine Eloquent kullanırsınız; desen sadece o kullanımı bir arayüz arkasına saklar. Eloquent'i tamamen bırakmak deseni kullanmanın bir koşulu değildir.

Laravel projenizde repository pattern gerçekten gerekli mi, yoksa daha sade bir mimari mi yeterli? Projenizin ölçeğine bakıp doğru soyutlama seviyesini birlikte belirleyebiliriz — benimle iletişime geçin.

Bu kategorideki tüm yazılar →

Devamı için