Laravel Repository Pattern

L

OOP ve çok katmanlı mimari yapısına sahip büyük projelerde gözardı edilen bazı ayrıntılar yazılımın ileriki aşamalarında önümüze bir çığ misali yığılıp kalmaktadır. Düzenli ve planlı yapılmadığı zaman proje kontrolden çıkabilir.

Bunun sonucunda bir işi N defa yapmak zorunda kalabiliriz, bu tekrarların önüne geçmek hem iş maliyeti açısından hemde projeye müdahale açısından son derece can alıcı bir konudur.

Büyük projelerde bu gibi durumların önüne geçebilmek için alt yapı aşamasında uygun bir yapı kurulması için dikkat edilmesi gereken şey Design pattern’ların kullanımıdır.

Repository Design Pattern, veritabanı sorumluluğunu üstlenen sınıfı tasarlarken bir standart üzerine oturtmayı hedefleyen ORM(Object Relational Mapping) araçlarıyla kombine edilerek sorgusal anlamda az sayıda metotla yüksek seviyede veri erişim imkanı sağlayan bir strateji üzerine kurulu tasarım desenidir.

Çalışma Mantığı

Yazılım uygulamaları genellikle yoğun bir şekilde veritabanı işlemleri gerçekleştiren ekosisteme sahip yapılar olduğundan dolayı, ilgili uygulamanın her bir noktasında gerekli veritabanı işlemlerini tekrar tekrar yazmak yerine bu işlemleri tekrar kullanılabilirlik prensibi (Reusability) çerçevesinde daha pratik bir şekilde tek seferde yapmamızı sağlayan bir yapı geliştirmemiz gerekecektir. İşte bu yapılanma Repository sınıfı olacaktır.

Repository sınıfı, içerisinde generic yapılanmalarla geliştirilen temel operasyonel veritabanı metotlarını barındıran bir sınıftır. Yukarıdaki diyagramdan da görüldüğü üzere sorgu generate etme ve genellikle ORM araçlarıyla kombine edilerek veri eşleştirme sorumluluğunu üstlenmektedir.

Repository Pattern Faydaları

  • Maintainability (sonradan bakım kolaylılığı) arttırmak
  • Kod tekrarlarından kaçınmak
  • Hata yakalamayı kolaylaştırmak
  • Kod yazımını ve okunuşunu kolaylaştırmak
  • Unit test yazabilmemizi kolaylaştırmak
  • Domain driven development’ın önünü açmak

Senaryo: Diyelim ki, bir Blog yazıları için api servisi sağlayan bi feature geliştirmemiz istenildi. Tipik bir geliştirme sürecinde aşağıda verilen kodları takip ediyoruz.

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class BlogController extends Controller
{
    public function index()
    {
        $data = Blog::all();
        return response()->json($data);
    }

    public function show(Request $request, $id)
    {
        $data = Blog::find($id);
        return response()->json($data);
    }

    public function store(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name'   => ['required'],
            'status' => ['required', 'boolean'],
        ]);

        if ($validator->fails()) {
            //code
        }
        //save
    }

    public function edit(Request $request, $id)
    {
        $data = Blog::find($id);
        return response()->json($data);
    }

    public function update(Request $request, $id)
    {
        $data = Blog::update($request->all());
        return response()->json();
    }

    public function delete($id)
    {
        $data = Blog::destroy($id);
        return response()->json($data);
    }
}

Problem:

Başka bir Controller’da blog yazısına veya listesine ihtiyaç duyduğumuzda o controllerda da aynı kodu tekrarlamak, yada Eloquent’ta o güne kadar ihtiyacımız olmayan bir veri kaynağını değiştirmek istediğimizde manuel kullandığımız tüm alanları değiştirmek zorunda kalırız, projenin büyüklüğüne göre bazen bunu yapmak oldukça imkansızdır, çünkü Controller’lar Model’e bağımlı oluyor

Çözüm:

Bu senaryoda, Laravel projemizdeki Repository pattern ile bu tür bir sorunu kolayca çözebiliriz. Verilere erişmek için Interface kullanarak Controller’da gevşek bir şekilde birleştirilecek, bağımsız olacak ve verilerin nerden geldiğini nasıl uygulandığını bilmesine gerek kalmayacak. Bu nedenle Repository de yapacağımız değişiklikleri Controllerda değiştirmemize gerek kalmayacak.

Repository Pattern’ı bu case üzerinden uygulamaya başlayalım;

App\Services\Interfacesdizini altında BlogInterface adında bir dosya oluşturalım, içerisinde index, byId, store, update gibi crud işlemleri yapacak methodlarımızı yazalım.

interface BlogInterface
{
    /**
     * @return Collection
     */
    public function index(): Collection;

    /**
     * @param int $id
     * @return Blog|null
     */
    public function byId(int $id): ?Blog;

    /**
     * @param Blog $model
     * @return bool
     */
    public function store(Blog $model): bool;

    /**
     * @param BlogRequest $request
     * @param int $id
     * @return bool
     */
    public function update(BlogRequest $request, int $id): bool;

    /**
     * @param int $id
     * @return bool
     */
    public function delete(int $id): bool;
}

Daha sonra App\Services\Repositories dizini altında BlogRepository.php adında dosyamızı oluşturalım.

class BlogRepository implements BlogInterface
{
    /**
     * @return Collection
     */
    public function index(): Collection
    {
        return Blog::all();
    }

    /**
     * @param int $id
     * @return Blog|null
     */
    public function byId(int $id): ?Blog
    {
        return Blog::query()->find($id);
    }

    /**
     * @param Blog $model
     * @return bool
     */
    public function store(Blog $model): bool
    {
        return $model->save();
    }

    /**
     * @param BlogRequest $request
     * @param int $id
     * @return bool
     */
    public function update(BlogRequest $request, int $id): bool
    {
        return Blog::query()->find($id)->update($request->all());
    }

    /**
     * @param int $id
     * @return bool
     */
    public function delete(int $id): bool
    {
        return Blog::destroy($id);
    }
}

İlgili sınıfımızda laravel eloquent model yapısını kullanarak örnek üzerinde blog modelinden giderek interface üzerindeki ilgili methodlarımızı geliştirdik ve şimdi sırada Provider’a register etmek kaldı. Yeni bir Service Provider oluşturuyoruz.

php artisan make:provider MyServiceProvider

Register methodunda bind ederken dikkat edilmesi gereken öncelikle interface sonrasında ise kullanacağınız repository olmalı.

class MyServiceProvider
{
    /**
     * Register Repository dependency injection
     *
     * @return void
     */
    public static function register(): void
    {
        app()->bind(BlogInterface::class, BlogRepository::class);
    }
}

Not: Provider, Laravel’in IoC Container sınıf bağımlılıklarını yönetmek ve bağımlılık enjeksiyonunu gerçekleştirme merkezidir.

Kendi servis sağlayıcılarımızı MyServiceProvider‘ı AppServiceProvider a entegre etmeyi unutmayalım.

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        // Migration length fix
        Schema::defaultStringLength(191);

        // Register Service IoC
        MyServiceProvider::register();
    }
}

BlogController adında bir Controller oluşturup oluşturduğumuz bu repositoryleri kullanma zamanı.

class BlogController extends Controller
{
    /**
     * @var BlogInterface
     */
    private BlogInterface $blog;

    /**
     * Create a new interface instance.
     * BlogController constructor.
     *
     * @param BlogInterface $blog
     */
    public function __construct(BlogInterface $blog)
    {
        $this->blog = $blog;
    }

    /**
     * Display a listing of the resource.
     *
     * @return JsonResponse
     */
    public function index(): JsonResponse
    {
        return Response::generateResponse(ResponseCode::SUCCESS,
            BlogResource::collection($this->blog->index()));
    }

    /**
     * Display the specified resource.
     *
     * @param int $id
     * @return JsonResponse
     */
    public function show(int $id): JsonResponse
    {
        return Response::generateResponse(ResponseCode::SUCCESS,
            new BlogResource($this->blog->byId($id)));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param BlogRequest $request
     * @return JsonResponse
     */
    public function store(BlogRequest $request): JsonResponse
    {
        $slug = Str::slug(request('title'));
        $request->merge([
            'slug'    => $slug,
        ]);

        $item = new Blog;
        $item->fill($request->all());
        $this->blog->store($item);

        return Response::generateResponse(ResponseCode::SUCCESS);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param BlogRequest $request
     * @param int $id
     * @return JsonResponse
     */
    public function update(BlogRequest $request, int $id): JsonResponse
    {
        $this->blog->update($request, $id);
        return Response::generateResponse(ResponseCode::SUCCESS);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param int $id
     * @return JsonResponse
     */
    public function destroy(int $id): JsonResponse
    {
        $this->blog->delete($id);
        return Response::generateResponse(ResponseCode::SUCCESS);
    }
}

__construct da ilgili bağımlılığımızı enjekte edip dilediğimiz gibi bağımlılığımızın içerisinde yer alan fonksiyonları kullanabiliriz. Repository Design Pattern ORM yapılanmalarıyla oldukça basit ve etkili veritabanı havuzu oluşturmamızı sağlamış ve daha kolay test edilebilir yapı hazırlamış olduk. Controller’da Repository yerine Interface implement edildiğine dikkat edin.

Daha fazla Repository’e ihtiyaç olursa?

Gereksinim için interface ve repository oluşturun ardından MyServiceProvider.php dosyasına ekleyin.

By Aydın Yağız

Aydın Yağız

Teknolojiye olan tutkumla inovasyonu destekler, kullanıcı deneyimini önceliklerim arasına alırım. Kendi yeteneklerimi ve bilgilerimi paylaşarak, daha geniş bir topluluğun faydalanmasını sağlarım. İş birliği içinde hareket ederek, geleceğin teknoloji dünyasına katkıda bulunmayı hedeflerim. Sizi de bu heyecan verici yolculuğa davet ediyor, fikirlerinizi paylaşmaya ve teknolojiye dair sınırları zorlamaya teşvik ediyorum. Birlikte büyüyelim ve yeni ufuklara açılalım!

İletişime Geçin

Kodlama dünyasına adım atın ve deneyimlerinizi paylaşın. Siz de bu aktif topluluğa katılarak yeni bağlantılar kurun, fikir alışverişinde bulunun ve bilgi birikiminizi artırın.

Özelleştir

Farklı yazı tipleri ve renk seçenekleriyle stilinizi kişiselleştirin. Aşağıdaki örneklerden birini deneyerek sizin için en uygun olanı seçin.

Yazı Tipi Örnekleri

Renk Örnekleri