blog.cemunalan.com.tr

ADR: Bir MVC Alternatifi

17 Ekim 2019

Action-Domain-Responder (kısaca ADR), Paul M. Jones tarafından 2014 yılında yayınlanmış, MVC’ye dair bir iyileştirme vaat eden bir teklif. Whitepaper olarak da adlandırabiliriz. ADR, MVC’nin modern web uygulamalarını açıklamada yetersiz kaldığını savunarak bir çözüm getiriyor. Aynı MVC’de olduğu gibi, kesin bir implementasyon belirtmiyor ancak MVC kadar da muğlak kalmamaya çalışıyor.

ADR’yi tanımadan önce MVC’nin tarihine bakalım istiyorum. MVC hangi problemleri çözmek için bulundu? Neden web uygulamalarında bu kadar benimsendi? Ne gibi faydaları ve zararları oldu?

MVC’nin Tarihi

Model View Controller üçlüsü, 1979 yılında Xeroc PARC’ta ziyaretçi olarak çalışan Trygve Reenskaug tarafından bulunuyor. Reenskaug o zamanlar bir petrol şirketi için üzerinde çalıştığı GUI program için bu çözümü geliştiriyor. İlk implementasyonu da kendisi Smalltalk ile yapıyor.

Reenskaug’un asıl amacı kullanıcıların programdaki kompleks bir veriye erişmelerini, onu değiştirebilmelerini, sonra da bu değişiklikleri görüntüleyebilmelerini sağlamak. Araştırmacının kişisel web sitesindeki görseli da o günkü niyetini açıkça belli ediyor.

https://heim.ifi.uio.no/~trygver/themes/mvc

Gördüğünüz üzere Controller ve View, domainimizin programdaki modelini insanların kullanımına açmak için iki tane araç. Programın ana parçası değiller. Asıl amacımız kullanıcıyı modele ulaştırmak.

Reenskaug’un kendisi de daha sonra bu sözle amacını açıklıyor:

“MVC was conceived as a general solution to the problem of users controlling a large and complex data set.”

Trygve Reenskaug, 1979. The Original MVC reports.
http://heim.ifi.uio.no/~trygver/2007/MVC_Originals.pdf

Buradan yola çıkarak MVC’nin ilk olarak bir GUI mimari çözümü olduğunu söyleyebiliriz. Günümüzde de halen GUI mimarisi altında tanımlandığını bazı kaynaklarda görebiliyoruz.

MVC’nin üç elemanının 1979’daki tanımlarına bir göz atalım.

Model, uygulamanın data ve business logic kısmı. Uygulamayı ve davranışlarını tanımlayan ana eleman.

Controller, kullanıcı aksiyonlarını (klavye veya mouse eventleri olarak düşünebiliriz) model’e aktarır.

View, modelin gösterimi. Model’deki değişiklikleri gözler (Observer pattern) ve buna reaksiyon gösterir.

Dikkat ederseniz Model ve Controller, bugün web için kullanılan MVC’deki tanımlarına çok benziyor. Ancak view biraz farklı. View katmanı modelden haberdar ve ondaki değişiklikleri direkt olarak kullanıcıya yansıtıyor. Bu memoryde çalışan stateful GUI uygulamaları için mümkün olan bir şey. Stateless HTTP üzerinden haberleşen uygulamalar için böyle bir implementasyon mümkün değil.

Web için MVC

Peki GUI uygulamalar için ortaya çıkan MVC neden ve nasıl web uygulamarında da bir norm haline geldi?

Maalesef bunun kesin tarihçesini bulamadım ancak belli başlı olaylar internette dökümante edilmiş durumda.

En büyük olay, Sun Microsystems’in JSP ile web uygulamaları geliştirme üzerine bir spesifikasyon yayınlaması ve bugün Model 2 diye anılan bir yöntem önermesi. Bu Model 2, Java komunitesinde “web için MVC” olarak yorumlandı (spesifikasyonda MVC’ye dair bir atıf geçmemesine rağmen). Kendisini MVC olarak tanımlayan ilk web frameworkler de bu spesifikasyondan sonra, 2000’lerin başında çıkamaya başladı.

İnternette Model 2’nin Java developerlarına açıklandığı pek çok makale bulabilirsiniz. Ben en çok referans gösterilenlerinden birini örnek olarak bırakayim.

Tahminim, MVC’nin bilinen bir konsept olması, web uygulamalarının da business logic ve presentation katmanı ayrımına fena halde ihtiyaç duyması sebebiyle MVC web içinde yer bulmaya başladı.

Elbette yukarıda bahsettiğim Model, View ve Controller tanımları web ortamında mümkün değildi. Örneğin HTTP’nin stateless yapısı sebebiyle, view’ın modeli sürekli olarak observe etmesi ve modeldeki değişiklikleri yansıtması mümkün değildi. Bu sebeple MVC’nin webdeki tanımı bir miktar değişmeye başladı. Örneğin, view katmanı controller tarafından oluşturulan HTML, JSON veya XML dökümanı olarak tanımlanmaya başladı.

Bu değişim sadece view katmanıyla da kalmadı. Eski yazıları inceledikçe Controller ve Model katmanı sorumluluklarının da karışmaya başladığını görebiliyorsunuz. Kendini MVC olarak duyuran frameworklerin ilki olan Struts‘ı tanıtan bu dökümanda controller şu şekilde tanımlanıyor:

“The Controller component receives the request. It then begins making deci-sions on how to proceed with processing based on the business rules encoded in it.”

Yine aynı kaynakta model tanımı ise şu şekilde:

“The Model components perform the actual interaction with the persistent datastores or remote systems that they manage interaction with.”

Gördüğünüz gibi tek görevi modele gelen kullanıcı aksiyonları iletmek olan controller artık web developerlar tarafından business logic de içerebilen bir katman olarak tanımlanıyor. Aynı şekilde, uygulamamızı ve domainimizi tanımlayan model katmanı, persistence layer katmanı olmaya indirgeniyor.

Görülüyor ki web developerların MVC yorumunda, controllerlar birden bire uygulama akışını yöneten, hangi modeli ne şekilde çağıracağını bilen, hangi view’ı nasıl render edeceğini bilen uygulamamızın ana ögesi haline geldiler.

Burada MVC’nin büyük anlam kaymalarından birinin yaşandığını söyleyebiliriz. MVC, bir GUI mimarisinden, web uygulama mimarisine dönüştü. Belki 90’ların sonu 00’lerin başında web uygulamalarındaki pek çok problemi çözdü ancak web uygulamaları büyüdükçe ve kompleksleştikçe bu düşünce yapısı insanları limitlemeye başladı.

Web için MVC’deki Bazı Problemler

Model bizim uygulamamızı tanımlayan bölümdü. Kelimenin anlamı değiştikçe, yavaş yavaş “veritabanına erişim katmanı” olarak düşünülmeye başlandı ve bizler domainimizi tarif edecek önemli bir alandan mahrum kaldık. Django, Ruby on Rails ve Laravel gibi popüler frameworkler, model kelimesine bir de veritabanı modellerinin ağırlığını yükledi ve bence işler iyice karıştı.

Dolayısı ile web frameworklerine yeni başlayan yazılımcılar kod yazılabilecek üç farklı yer olduğunu düşünerek web frameworklerinde çalışmaya başladılar: Model (DB katmanı), Controller (Network ve business logic katmanı) ve View (HTML, JSON etc.). Ben de bir süre bu haldeydim.

Web uygulamaları karmaşıklaştıkça MVC’nin görüş bildirmediği pek çok farklı alan çıktı. CLI, Queue consumerları, admin aksiyonları, bildirimler, loglamalar… Web uygulamalarımızı MVC olarak tanımladıkça bunları nereye yerleştireceğimiz bilemez olduk.

ADR

Action-Domain-Responder, 2014 yılında Paul M. Jones tarafından duyuruldu. Paul M. Jones, PHP komunitesinde oldukça aktif, bazı PSR standartlarının belirlenmesinde görev almış bir adam. Dolayısı ile komunitede belirli bir kredibilitesi var.

ADR, HTTP üzerinden haberleşen web uygulamalarını tanımlamak üzere geliştirilmiş bir tanımlama. Net bir implementasyonu yok. Vurguladığı önemli noktalardan biri, bir uygulama mimarisi olmaması ve sadece web uygulamaları için bir kullanıcı etkileşimi pattern’i olması.

ADR elemanları olan action, domain ve responder’ı MVC’deki elemanlara benzetebiliriz bir noktada.

Domain, MVC’deki modelin aynısıdır. Sadece bahsettiğim “Model” kelimesinin anlam değişmesine son vermek için yeniden isimlendirilmiştir.

Action, HTTP requestlerini karşılayan ve uygulamadaki bir aksiyonu niteleyen elemandır. Tek görevi gelen request’i ilgili domain’e iletmek, domain’den gelen cevabı da uygun responder’a göndermektir. Action’da domaine dair bir validasyon bile yapılması önerilmiyor, ne kadar uygulamadan habersiz ve aptal olursa o kadar iyi. Bu kullandığımız framework’ün domain’e sızmaması, framework bağımsız kod yazabilmemiz için önemli.

Responder, MVC’deki view katmanına benzer ancak sadece HTML veya JSON içerikten ibaret değildir. Action’dan aldığı domain cevabı ile bir HTTP response oluşturur. Headerlar ve HTTP durum kodları dahil olmak üzere HTTP response’u oluşturmak tamamen responder’ın sorumluluğudur.

Basit Bir ADR Örneği

Basit bir akış

Burada bir Action sınıfını görüyoruz. Bu sınıf sadece yeni bir blog post oluşturma request’ini karşılıyor. HTTP request’inden alması gereken verileri alıp domain’e bunları gönderiyor. Yukarıda dediğimi tekrarlayacak o olursam burada domain’e dair bir validasyon yapılmıyor. Ancak HTTP’ye dair bir validasyon yapılabilir, örneğin gelen JSON body’si geçerli mi değil mi gibi.

Domian’e gerekli komutu verdikten sonra dönen cevap Responder’a aktarılıyor ve Action’ın görevi burada bitiyor. ADR teklifinde, Action katmanının özellikle logic barındırmaması vurgulanan noktalardan biri.

Dediğim gibi ADR’nin kesin bir implementasyonu olmadığı için üstteki örnekte olduğu gibi her Action kendi responder’ına sahip olacak diye bir kural yok. Burada göreceğiniz gibi jenerik bir JsonResponder veya HTML responder da oluşturabilirsiniz.

Bugün Ne Yapabiliriz?

Elbette yarın oturup ADR’ı benimseyelim, başka bir şey kullanmayalım demiyorum. Ancak bu MVC vs ADR tartışmasından çıkarılacak değerli dersler olduğunu düşünüyorum. Zaten bu yazıyı yazmamın sebebi de bu.

1) Domainimiz Hakkında Düşünmeliyiz

Kullandığımız frameworklerin uygulamamızı tanımlamasına izin vermemeliyiz.

Genel olarak Web uygulamaları diyince aklımıza gelen bu:

Ancak gerçekte olan bu:

Modelimiz/domainimiz uygulamanın en büyük ve en kritik kısmını oluşturuyor. Onun geliştirilmesi, maintainable olması çok önemli. O yüzden MVC’deki “model” kelimesinden kurtulup modern pratiklerle modelimizin tasarımına dikkat etmeliyiz. Bugün bunlar için pek çok araç var elimizde. DDD, CQRS, Ports and Layers (Hexagonal mimari olarak da biliniyor), mikroservisler…

2) Single Action Controllers

Günlük kodlama yaparken beni en çok rahatlatan şey bu oldu. Her controller’da sadece bir tane aksiyon gerçekleştirmek.

Açıkçası neden bir controller’da birden fazla aksiyon bulunması bir problem olarak gösterilmiyor anlamış değilim. Sanırım controller framework’ün büyülü bir sınıfı olarak görülüyor ve o sebeple çok fazla bunun üzerine kafa yorulmuyor. Halbuki her controller bir aksiyondan sorumlu olsa (SRP diye de okuyabiliriz burayı) uygulamayı anlaması ve takip etmesi ciddi manada kolaylaşıyor.

PHP uygulamalarında sıklıkla karşınıza çıkacak bir controller’a bakalım (bu konuda yaptığım sunumumun slaytlarından alınmıştır).

Burada çok acayip bir şey yok. Bir resource’u (BlogPost) etkileyen değişiklikler tek sınıf içerisinde basitçe metodlarına ayrılmış. Problem biraz bu metodların herbirinin içinde barınan logic arttıkça ortaya çıkmaya başlıyor. Logic yanında, controller içinde ihtiyaç duyulacak yardımcı private metodlar da işin içine girince ortalık karışıyor.

Yardımcı metodları ilgili public metodun hemen altına yerleştirince bu şekil bir çaprazlama oluyor. Bu da sınıfın okunmasını ve anlaşılmasını zorlaştırıyor.

Uzayan metodlar, controller’ın public metodlarını sayfanın aşağısına doğru ittikçe tek bakışta controller’ı anlamak zorlaşıyor. Uygulamanın sahip olduğu public metodlar, ya da aksiyonlar tek bakışta anlaşılamıyor. Bu gibi durumlarda codebase’de belirli bir aksiyon kolay bulunamadığı için, kelime arayarak gezmek durumunda bile kalınıyor. Bu da bence oldukça sıkıntılı bir durum.

Bu gibi sebeplerle ADR’deki Action konseptini benimseyip, controllerlarımızdaki aksiyon sayısını bire düşürebiliriz. Sonunda bunun gibi daha anlaşılabilir controllerlara erişebiliriz.

Her controller başında bağımlılıkları (DB katmanı, cache katmanı, Loglama katmanı gibi) deklare edilir. Aksiyonun kendisi bağımlılıklardan sonra gelir ve onun altına da varsa gerekli yardımcı metodlar sıralanır. Bütün controllerlarda bu akış takip edildiğinde codebase içerisinde gezinmesi, bir kaç sene önce yapılmış featureların yerini bulması kolaylaşıyor.

Üstelik private metodlarınız sadece bu aksiyonu ilgilendirdiği için, yaptığınız değişikliklerin nereyi etkileyincenden emin olarak geliştirme yapabilirsiniz.

Bunun ekstra bir getiris de şu, dosya sistemine bakarak, controllerları gezmeden uygulamanızda ne gibi aksiyonlar var görebiliyorsunuz.

3) View != Template

Genellikle controllerlar şu yukarıdaki aksiyonlarla sonlanır. Controller hangi template’i render edeceğini, hangi status code ile hangi header ile yollayacağını bilir. Buna göre geliştirme yapılır.

Twig render etmek veya API için bir json sonuç göndermek oldukça kolay, düzgün abstractionları olan basit hareketler. Ancak her zaman response’umuz bundan ibaret olmuyor.

  • API sonuçlarımızı dekore etmek istersek
  • Cache için belirli sayfalarda belirli headerlar eklemek istersek
  • Dosya download edilmesi için ekstra headerlar eklemek istersek

Örneğin bu benim vakti zamanında yazdığım bir Excel export kodu. Controller’ın bitiminde hemen sonra duruyor. Oldukça karışık, tamamen kullanıcını tarayıcısındaki davranışı yönetmeye yönelik bir kod parçası. Bunun controller içinde bulunması bence gereksiz karışıklık yaratıyor. Bunun sebebi de view denince sadece template’leri veya Response’un gövdesini düşünmemiz. Headerları, status code’ları atlamamız.

Çözüm view katmanını tüm HTTP response olarak düşünmek. ADR’deki gibi ayrı Responder sınıfı olsun da demiyorum. Sadece sorumluluklar daha net ayrılsın. Controller içinde yardımcı private metod bile iş görecektir.

Örneğin bu bir API response’u dönen kod. Yardımcı bir metoda bu görevi verebiliriz. Ya da daha iyisi, kendi yarattığımız bir ApiResponse sınıfı yaratabiliriz. Bu response’a Decorator pattern‘i ile header, status code gibi modifikasyonlarda bulunup response’umuzu oluşturabiliriz.