Bir 'Controller' nasıl olmalı?

Bu günlerde web geliştirme çatılarının (framework) neredeyse tamamı MVC (Model-View-Controller) modellemesini kullanmakta, ancak son zamanlarda incelediğim web uygulamalarında controllerın amaçları dışında aşırı yüklendiğini görüyorum ve bu konuda uzman olan kişilerden gördüklerimi, kendi deneyimlerimi paylaşmak istiyorum.

Öncelikle MVC yapısının en temel kanunu kabaca MVC’yi oluşturan tüm bileşenlerin birbirinden bağımsız alanlarda olmasıdır. Bu bize bir View içerisinde database bağlantısı yapmamak, controller içerisinde string işlemleriyle HTML, JavaScript üretmemek gerektiğini söyler. Bunlar çok abartı örnekler olmasına rağmen hala MVC destekli çatılar altında böyle davranışlar söz konusu.

Bu abartı örnekleri bi kenara bırakıp, MVC mantığına daha uygun davranılan ama yine de controllerların aşırı şiştiği durumları düşünelim ve bir controllerın nasıl şekillenmesi gerektiğine biraz eğilelim. Öncelikle web uygulamalarında bir isteğin işlenmesi genel olarak aşağıdaki aşamaları içermekte;

  • Authentication
  • Authorization
  • Validation
  • Process
  • Response

Bu aşamaların üstüne örnek bir senaryo giydirmeye çalışalım ve bir okul sisteminde öğrenci kayıt yapan bir aksiyonu düşünelim. Burada istek alındığında yapılması gereken ilk şey, “authentication” yani isteği gönderen kişinin sistem tarafından tanınıp tanınmadığı olmalıdır. Yeni nesil çatılarda bu aşama genelde controller’ların üstünde ayrı bir katman olarak tutulur. Laravel Framework’u göz önüne aldığımızda genelde bu işi “Middleware” kullanarak hallederiz.

Kişinin kim olduğunu tespit ettikten sonra asıl olan o kişinin öğrenci kaydını yapıp yapamayacağını belirlemektir. Buna yetkilendirme (authorization) denir, aslına bakılırsa çok geniş bir konudur ve ayrıca incelenmesi gerekir. Yine Laravel Framework’den örnek verirsek, çoğu durumda yine Middleware kullanarak yetkilendirme işlemini çözebiliriz. Ancak ben burada bunu bir kenara bırakıp controller içerisinde yapmayı tercih edeceğim. Dolayısıyla öğrenci kayıdı alan aksiyonumuzun içerisinde yazacağımız ilk satırların yetkilendirme için olacağını söyleyebiliriz.

class StudentController extends BaseController {
    public function save(Request $request) {
        // Authorization
        if ($request->user()->cannot("add_new_student")){
            abort(403);
        }
    }
}

Daha sonraki işlem, bu öğrenci kaydının yapılabilmesi için gerekli parametrelere sahip olup olmadığımızın kontrolüdür. Bir sistemde doğru parametrelerin, doğru şekilde(!) gönderilip gönderilmediğinin kontrol edilmesi hem güvenlik hem de veri bütünlüğü açısından önemlidir ve bu işleme doğrulama (validation) denir.

class StudentController extends BaseController {

    public function save(Request $request) {
        // Authorization
        if ($request->user()->cannot("add_new_student")){
            abort(403);
        }

        // Validation
        $this->validate($request, [
            'name' => 'required',
            'surname' => 'required',
            'email' => 'required|email'
            ...
        ]);
    }

}

Bu aşamayıda geçtikten sonra artık istenen işlemi yapmanın vakti gelmiş demektir. Ancak ne yazık ki çoğu kişinin direkt olarak burada gerekli modelin fonksiyonlarını çağırarak veritabanına kayıt eklediğini görüyoruz. Ki işler bu safhada karışıyor diyebiliriz. Burada save() aksiyonu, her ne kadar ismi çok uygunsa da, öğrenciyi kaydetmekle yükümlü bir fonksiyon olmamalıdır. Büyük projelerde direkt modeli burada kullanmak pek akıllıca olmayacaktır. Burada araya “Repository” dediğimiz bir katmanı ekliyoruz. Repository kavramını daha iyi anlamak için buradaki paylaşımı kullanabilirsiniz.

class StudentController extends BaseController {
  
    public function save(Request $request, StudentRepository $studentRepository) {
        // Authorization
        if ($request->user()->cannot("add_new_student")){
            abort(403);
        }
        // Validation
        $this->validate($request, [
            'name' => 'required',
            'surname' => 'required',
            'email' => 'required|email'
            ...
        ]);

        // Process
        $student = $studentRepository->create($request->all());
    }

}

İşlemi gerçekleştirdikten sonra tabi ki kullanıcıya bir geri dönüş yapmak zorundayız. Buradaki dönüş tamamen geliştirdiğiniz sisteme özgü olmakla birlikte, bunun bir API olduğunu farz edersek, aşağıdaki gibi bir yöntem izleyebiliriz.

class StudentController extends BaseController {

    public function save(Request $request, StudentRepository $studentRepository) {
        // Authorization
        if ($request->user()->cannot("add_new_student")){
            abort(403);
        }
        // Validation
        $this->validate($request, [
            'name' => 'required',
            'surname' => 'required',
            'email' => 'required|email'
            ...
        ]);

        // Process
        $student = $studentRepository->create($request->all());

        // Response
        if (!$student) {
            return ['status'=> false,'error_code' => xxxx, 'error' => 'Error Message'];
        }

        return ['status' => true, 'data' => $student];
    }

}

Gördüğünüz gibi bir aksiyonun yazılması için 20 satır yeterli olabiliyor. Burada yaptığınız iş sadece öğrenci kaydetmek olmayabilir. Öğrencinin veli bilgilerini farklı bir yere, sınıf bilgilerini farklı bir yere yada daha bir çok bilgiyi farklı bir yere yazıyor olabilirsiniz. Yeni bir öğrenci geldiğinde arama sistemine bir istekte bulunuyor, öğrenciye ayrıca bir sistemden mail adresi açıyor veya daha bir çok şey yapıyor olabilirsiniz. Ancak bunların hiçbiri controllerımın basit bir save fonksiyonunu ilgilendirmiyor. Bırakın bunlarla “Repository”,“Event” gibi bileşenler ilgilensin.

Keep it simple, stupid