Bir junior geliştiriciye bir parça mantığın nerede bulunması gerektiğini sorarsanız genellikle iki cevap alırsınız: controller veya model. Bir mid-level geliştiriciye aynı soruyu sorduğunuzda, genellikle “bu ne tür bir mantık?” diye geri sorar. Bu ayrım, bu makalenin kalbidir.
Zihinlerde oturmuş “separation of concerns” (sorumlulukların ayrılması) ilkesi, kariyerinizin başlarında duyduğunuz ve anlamış gibi göründüğünüz bir kavramdır. Ancak, üç yüz satır büyüklüğünde bir controller ile karşılaştığınızda, işlerin neden bu hale geldiği konusunda bir kavram kargaşası yaşarsınız.
Bu yazıda, separation of concerns’ın bir Laravel uygulaması içindeki anlamını inceleyeceğiz. “Fat controllers” kavramının, bir kod kalitesi problemi değil, bir mimari boşluğun belirtisi olduğunu göreceğiz ve kodun nerede bulunması gerektiğiyle ilgili karar alma sürecimizi katmanlar açısından yeniden değerlendireceğiz.
Layer Nedir?
Layer Nedir?
Bir layer, belirli bir işle meşgul olan kod grubudur. Layer içindeki kod yalnızca o işin yürütülmesiyle ilgilenmelidir. Farklı bir iş yapması gerektiğinde, bunu gerçekleştirmek için farklı bir layer’a devretmelidir.
Bir Laravel uygulamasında benim kullandığım katman yapısı şu şekildedir:
HTTP Layer: Gelen istek ve giden yanıtlarla ilgili her şeyi kontrol eder. Controller, middleware, form request ve yanıtlar bu katmanda yer alır. Bu katmanın görevi girişi almak, doğrulamak, iş katmanına iletmek ve bir yanıt döndürmektir. İş kararları vermez ve doğrudan veritabanına sorgu yapmaz.
Business Layer: Uygulamanızın yapması gereken mantığı içerir. Burada işlemler, servisler ve domain nesneleri bulunur. Bu katman HTTP ile ilgili hiçbir şeyi bilmez. Verileri alır, kuralları uygular ve sonuçlar döndürür. Kayıtları okumak veya yazmak için veri katmanıyla iletişim kurabilir.
Data Layer: Kalıcılığı yönetir. Modeller, sorgu builder’ları ve repository sınıflarını içerir. Bu katman, kayıtları bulma, oluşturma, güncelleme ve silme işlemlerini bilir. İş kurallarını uygulamaz ve HTTP ile ilgili bir bilgisi yoktur.
Üç katman. Üç farklı görev. Her biri kendi sorumluluklarını bilmekte ve diğerleriyle ilgilenmemektedir.
Fat Controllers Neden Ortaya Çıkar?
Fat Controllers Neden Ortaya Çıkar?
Fat controller, bir disiplin problemi değildir. Bu, mimari bir boşluktan kaynaklanmaktadır.
Eğer bir ekip, nerede neyin bulunması gerektiği konusunda net bir anlayışa sahip değilse, mantık en belirgin yere, yani controller’a toplanacaktır. Controller, isteğin geldiği yer olduğu için, geliştiriciler genellikle kod yazmaya burada başlar. Kod oraya yerleştirildikten sonra, ek kod eklemek daha kolaydır, çünkü mevcut bir dosyayı genişletmek yeni bir dosya oluşturmaktan daha pratiktir.
Sonunda, bir controller’ın girişi doğrulaması, veritabanına göz atması, e-postalar göndermesi ve daha birçok işlemi gerçekleştirmesi durumu ortaya çıkar. Hepsini absorbe etmiştir çünkü hiçbiri belirgin bir ev sahipliği yoktur.
Çözüm, controller’ı yeniden yapılandırmak değil; her tür mantığa bir yer vermektir ki geliştiriciler her zaman yeni kodu nereye yerleştireceklerini bilsinler.
Katmanları Kullanma ve Netlik
Katmanları Kullanma ve Netlik
Örneğin, bir talep gönderim özelliğini inceleyelim.
Bir müşteri yeni bir talep göndermektedir. Yapılması gerekenler:
- Girişi doğrulamak (başlık zorunlu, açıklama zorunlu, ekler isteğe bağlı)
- Müşterinin organizasyonu ile ilişkili talep kaydını oluşturmak
- Yüklenen ekleri depolamak
- Başlangıç durumunu “gönderildi” olarak ayarlamak
- Gönderimi onaylayan bir yanıt döndürmek
Katmanlar olmadan, tüm bu mantık genellikle controller içinde toplanır. İşte bunun nasıl göründüğü ve neden bir problem yarattığı:
// Kaçınılması gereken türde bir controller
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'description' => ['required', 'string'],
'attachments.*' => ['nullable', 'file', 'max:10240'],
]);
$projectRequest = ProjectRequest::create([
'organisation_id' => auth()->user()->organisation_id,
'submitted_by' => auth()->id(),
'title' => $validated['title'],
'description' => $validated['description'],
'status' => 'submitted',
]);
if ($request->hasFile( {
foreach ($request->file( as $file) {
$path = $file->store(
$projectRequest->attachments()->create([
=> auth()->id(),
=> $file->getClientOriginalName(),
=> $path,
=> $file->getMimeType(),
]);
}
}
return response()->json([=> $projectRequest->load( 201);
}
Bu kötü bir kod değil. Çalışıyor. Ama bir sürü işlemi aynı anda yapıyor. Girişi doğruluyor, kayıtları oluşturuyor, dosya depoluyor ve yanıtı formatlıyor; bunların hepsi aynı yöntemde gerçekleşiyor. İzolasyonda test etmesi zor çünkü HTTP istek nesnesine sıkı bir şekilde bağlı. Mantıkların yeniden kullanılması (örneğin, taleplerin bir import iş üzerinden de gönderilmesi durumunda) kopyalama ya da zorlandığında çıkarma anlamına gelir.
Şimdi, katmanlar uygulanmış aynı özelliği görüyoruz:
// Form Request doğrulamayı yönetir
class StoreRequestRequest extends FormRequest
{
public function rules(): array
{
return [
=> [, , ],
=> [, ],
[, , ],
];
}
public function payload(): StoreRequestPayload
{
return new StoreRequestPayload(
organisationId: auth()->user()->organisation_id,
submittedBy: auth()->id(),
title: $this->input(),
description: $this->input(),
attachments: $this->file( []),
);
}
}
İş mantığını yöneten eylem sınıfıdır.
// Eylem iş mantığını yönetir
final readonly class SubmitRequest
{
public function execute(StoreRequestPayload $payload): ProjectRequest
{
$projectRequest = ProjectRequest::create([
=> $payload->organisationId,
=> $payload->submittedBy,
=> $payload->title,
=> $payload->description,
=> ,
]);
foreach ($payload->attachments as $file) {
$path = $file->store(
$projectRequest->attachments()->create([
=> $payload->submittedBy,
=> $file->getClientOriginalName(),
=> $path,
=> $file->getMimeType(),
]);
}
return $projectRequest;
}
}
// Controller HTTP giriş ve çıkışını yönetir
final readonly class StoreController
{
public function __construct(
private SubmitRequest $submitRequest,
) {}
public function __invoke(StoreRequestRequest $request): JsonResponse
{
$projectRequest = $this->submitRequest->execute(
payload: $request->payload(),
);
return response()->json(
[=> $projectRequest->load(
201,
);
}
}
Controller now dört satırlık anlamlı bir mantığa sahip. Doğrulanan bir yük alıyor, bir eylem çağrısı yapıyor ve bir yanıt döndürüyor. İsteğin nasıl oluşturulduğunu veya eklerin nasıl depolandığını bilmiyor. Bunlar eylemin işi.
Model Nerede Yer Alır?
Model Nerede Yer Alır?
Yukarıdaki kodda modelin yalnızca ProjectRequest::create() ve attachments() ilişkisi gibi kısımlarda bulunduğunu göreceksiniz. Bu, kasıtlıdır.
Katmanlı bir uygulamada modeller, bir veritabanı kaydını ve onun ilişkilerini temsil etmekle mükelleftir. İş kuralları, doğrulama veya uygulama mantığı ile ilgilenmezler. submitAndNotifyClient() veya assignToAvailableDeveloper() gibi metotlara sahip bir model, eylemin görevini yerine getiriyor demektir ve bu mantığın büyümesiyle test edilmesi ve bakımı zorlaşır.
Modelleri odaklı tutun. Tablo, sütunları, ilişkileri ve cast’leri hakkında bilgi sahibidirler. Geri kalan her şey, onların üstünde bir katmanda yer almalıdır.
Bu Bir Dogma Değildir
Bu Bir Dogma Değildir
Katmanların bir araç olduğunu ve bir din olmadığını belirtmek isterim. Bir kayıt okuyan ve döndüren basit bir CRUD endpointi için bir eylem sınıfına ihtiyaç yoktur. Gerekmediği yerde dolaylılık eklemek, değer yaratmadan gürültü oluşturur.
Sorulması gereken soru: bu mantık yeterince karmaşık mı veya yeniden kullanılabilir mi, kendi evine sahip olmayı hak ediyor mu? Cevap evet ise, çıkarın. Hayır ise, controller’da tutun ve yolunuza devam edin.
Mid-level geliştiriciler bu kararı verme yeteneğini geliştirir. Juniors genellikle bağlamdan bağımsız olarak kalıpları uygulamakta, bu da basit şeyleri gereksiz yere karmaşık hale getirir. Amaç, kural takibi değil, yargıda bulunmaktır.
Uygulama
Uygulama
Üzerinde çalıştığınız bir projeden bir controller alın ve ona dürüstçe bakın. Kaç iş yapıyor? Hangi satırların HTTP layer’a, hangilerinin business layer’a ve hangilerinin data layer’a ait olduğunu belirleyebilir misiniz?
Şu an yeniden yapılandırmanız gerekmiyor. Sadece gördüğünüz şeyleri kategorize etme eylemini pratiğe dökün. O kategorileme içgüdüsü, inşa ettiğiniz bir şeydir ve daha çok uygulamadan gelir, okumaktan değil.
Bir sonraki makalede, her şeyi bir araya getireceğiz ve ERD’nizi ve mimari diagramınızı bir geliştiricinin gerçekten takip edebileceği ardışık bir yapı planına nasıl dönüştüreceğimizi inceleyeceğiz.
Kaynak: Orijinal Makale


