Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Front Controller Design Pattern Nedir? Nasıl Uygulanır?

Merhaba,

Bu içeriğimizde yapısal desenler(structural patterns) kategorisiyle birlikte bir yandan da web arayüz desenleri kategorisinde de adından bahsettiren Front Controller Design Pattern üzerine konuşuyor olacağız. İçeriğimiz süresince Front Controller Design Pattern nedir? ne amaçla ve nasıl kullanılmaktadır? sorularını sorarak cevaplandırmaya çalışacak ve pratiksel olarak örnekler sunacağız.

Front Controller Design Pattern’ın Amacı Nedir?

Front Controller Design Pattern’in amacı, client’lardan gelecek olan istekleri merkezi bir yerde karşılayıp sonrasında ilgili kod parçacıklarına ya da bir başka deyişle istekle alakalı kodu/iş mantığını barındıran sınıflara yönlendirmektir. İstekleri merkezi bir yerde karşılamasının temel nedeni, farklı iş mantıklarında sürekli ortak işlemlerin var olmasıdır. Misal olarak, kullanıcılardan gelen istekler her ne olursa olsun öncelikle bir yetkilendirme kontrolü, loglama ve takip gibi türlü işlemler gerektiriyor olabilir. Ya da yetkili bir kullanıcının erişmek istediği verilere rolünün müsaade edip etmediği de kontrol gerektirebilir. Bu tarz kontrolleri her bir işlem için tekrarlı bir şekilde ayrı ayrı yapmak yerine istekleri merkezi bir controller’da karşılayarak, öncelikli olarak orada tek elden gerçekleştirip sonra isteği ilgili koda yönlendirmek tasarımsal açıdan doğru bir yaklaşım olacaktır ve tekrar eden kod miktarını düşürecektir.

Front Controller Design Pattern Nasıl Çalışır?

Genellikle web uygulamalarında tercih edilen Front Controller Design Pattern gelen bütün istekleri karşılayan bir controller sınıfı üzerinden işlemleri yürütmektedir. Yapısal olarak belli başlı aktörleri aşağıdaki şemada olduğu gibidir;
Front Controller Design Pattern Nedir? Nasıl Uygulanır?

  • Front Controller
    Uygulamada gelen her isteği karşılayan sınıftır.
  • Dispatcher
    Front Controller tarafından karşılanan isteği ilgili handler’a yönlendirmeyi sağlayan nesnedir.
  • Handler
    İstek sürecinde gerekli işlemi gerçekleştirecek ve hatta varsa veriyi üretecek sınıftır.
  • View
    İstek neticesinde Handler tarafından işlenerek üretilen sonucun client’a sunulduğu yapıdır.

Bu tasarımda, Front Controller sınıfının ihtiyacı olan tüm bilgiler client’ın yaptığı istekler içerisinde context olarak gönderilmektedir. Misal olarak; Front Controller’a gelen istekte hangi handler sınıfının tetikleneceği, handler’ın hangi view yapısını çalıştıracağı vs. tarzı bilgiler request içerisinde parametre olarak gelmektedir ve gerektiği taktirde if/else kontrol yapılarıyla bu analiz edilerek karar verilir.

Pratik Yapalım

Şimdi konuya dair bir senaryo üzerinden pratik örnek vererek ilerleyelim.

Senaryo
Bu senaryoda basit bir Console Application üzerinden Front Controller Design Pattern’ın nasıl uygulandığını ele alıyor olacağız.

Senaryo mahiyet açısından şöyle olacaktır; bir banka uygulaması geliştirdiğimizi düşünelim. Bu uygulamada client’lar tarafından yapılan tüm istekler merkezi bir sınıf olan BankaController(Front Controller) tarafından karşılanacak ve yapılan isteği ya GarantiHandler(Handler) ya da HalkbankHandler(Handler) nesnelerinden ilgili olanına yönlendirecek.

Çözüm

  • Adım 1
    Herşeyden önce GarantiHandler(Handler) ve HalkbankHandler(Handler) sınıflarını oluşturarak başlayalım.

        public class GarantiHandler
        {
            public void FaturaOde(decimal tutar)
                => Console.WriteLine($"{tutar} TL tutarında fatura ödenmiştir.");
            public void EFT(decimal tutar, string hesapNo)
                => Console.WriteLine($"{tutar} TL tutarında para {hesapNo} numaralı hesaba gönderilmiştir.");
        }
    
        public class HalkbankHandler
        {
            public void KrediCek(int tutar)
                => Console.WriteLine($"{tutar} TL tutarında kredi çekilmiştir.");
        }
    

    Handler sınıfları birbirlerinden farklı olabilirler. Dolayısıyla yukarıdaki tanımlanan sınıflardaki memberların birbirlerinden farklı olduğuna dikkatinizi çekerim.

  • Adım 2
    Handler sınıfları oluşturulduktan sonra isteğe göre hangi handler’ın tetikleneceğinin kararını verecek ve yönlendirme işlemini üstlenecek olan BankaDispatcher(Dispatcher) nesnesini oluşturmamız gerekmektedir.

        public class BankaDispatcher
        {
            readonly GarantiHandler _garantiHandler;
            readonly HalkbankHandler _halkbankHandler;
    
            public BankaDispatcher()
            {
                _garantiHandler = new();
                _halkbankHandler = new();
            }
    
            public void Dispatch(Banka banka, Islem islem, object[] values)
            {
                var process = (banka, islem) switch
                {
                    (Banka.Garanti, Islem.FaturaOde) => 1,
                    (Banka.Garanti, Islem.EFT) => 2,
                    (Banka.Halkbank, Islem.KrediCek) => 3,
                };
    
                if (process == 1)
                    _garantiHandler.FaturaOde((decimal)values[0]);
                else if (process == 2)
                    _garantiHandler.EFT((decimal)values[0], (string)values[1]);
                else if (process == 3)
                    _halkbankHandler.KrediCek((int)values[0]);
            }
        }
    

    Yukarıdaki BankaDispatcher(Dispatcher) sınıfını incelerseniz eğer içerisinde GarantiHandler(Handler) ve HalkbankHandler(Handler) sınıflarının referanslarını barındırmakta ve constructor içerisinde bu referanslara birer instance üretip, bağlamaktadır. ‘Dispatch’ metoduna göz atarsanız eğer BankaController(Front Controller) tarafından karşılanacak isteklerdeki parametrelere göre yapılacak işlemi analiz etmekte ve gerekli handler’ı tetiklemektedir.

    Burada kullanılan ‘Banka’ ve ‘Islem’ enum’larının içeriğide aşağıdaki gibi olacaktır.

        public enum Banka
        {
            [Display(Name = "Garanti Bankası")]
            Garanti,
            [Display(Name = "Halk Bankası")]
            Halkbank
        }
    
        public enum Islem
        {
            [Display(Name = "Fatura Ödeme")]
            FaturaOde,
            [Display(Name = "EFT")]
            EFT,
            [Display(Name = "Kredi Çekme")]
            KrediCek
        }
    
  • Adım 3
    Ardından client tarafından gelecek olan istekleri karşılayacak olan BankaController(Front Controller) sınıfını oluşturalım.

        public class BankaController
        {
            readonly BankaDispatcher _dispatcher;
            public BankaController()
                => _dispatcher = new();
    
            bool IsAuthenticUser()
            {
                Console.WriteLine("Kullanıcı başarıyla doğrulanmıştır.");
                return true;
            }
            void SetLog(Banka banka, Islem islem)
                => Console.WriteLine($"{GetDisplayName(banka.GetType(), banka.ToString())} bankasında {GetDisplayName(islem.GetType(), islem.ToString())} yapılmıştır.");
    
            string GetDisplayName(Type type, string member)
                => type
                .GetMember(member)
                .First()
                .GetCustomAttribute<DisplayAttribute>()
                .Name;
    
            public void DispatchBanka(Banka banka, Islem islem, object[] values)
            {
                if (IsAuthenticUser())
                {
                    _dispatcher.Dispatch(banka, islem, values);
                    SetLog(banka, islem);
                }
            }
        }
    

    Yukarıda BankaController(Front Controller) sınıfının kodunu incelerseniz eğer içerisinde tüm isteklerdeki öncelikli ve tekrar işlemler olan kullanıcı kimlik doğrulama, loglama vs. gibi ‘IsAuthenticUser’, ‘SetLog’ operasyonlarını barındırmaktadır. Bu operasyonlar eşliğinde referans ettiği BankaDispatcher(Dispatcher) nesnesi üzerinden gerekli handler sınıflarını tetiklemektedir.

  • Adım 4
    Artık sıra inşa edilen bu yapıyı kullanmaya gelmiştir.

        class Program
        {
            static void Main(string[] args)
            {
                BankaController bankaController = new();
                bankaController.DispatchBanka(Banka.Garanti, Islem.EFT, new object[] { (decimal)1000, "8123123" });
                Console.WriteLine("*****");
                bankaController.DispatchBanka(Banka.Garanti, Islem.FaturaOde, new object[] { (decimal)250 });
                Console.WriteLine("*****");
                bankaController.DispatchBanka(Banka.Halkbank, Islem.KrediCek, new object[] { 25000 });
            }
        }
    

    Yukarıdaki kod bloğunu incelerseniz eğer Front Controller tasarımını uyguladığımız BankaController(Front Controller) sınıfından bir nesne oluşturulmakta ve farklı parametreler eşliğinde üç istek gerçekleştirilmektedir. BankaDispatcher(Dispatcher) nesnesinde bu istekteki parametrelerin mahiyetine uygun bir şekilde hangi handler’ın tetikleneceği analiz edilmekte ve sonuç aşağıdaki gibi olmaktadır.
    Front Controller Design Pattern Nedir Nasıl Uygulanır
    Görüldüğü üzere, isteğin ilgilendiği handler farketmeksizin her bir istekte yapılması gereken tüm sabit işlemleri tekrar tekrar yapmaktansa Front Controller deseni sayesinde tek bir merkezi sınıfta yapıp, beklenen işleri daha az maliyetle gerçekleştirmiş bulunmaktayız.

Ne Zaman Kullanılır?

Uygulamadaki request’ler işlenirken ortak işlemlerin sayısı haddinden fazlaysa ve anlamsız boyutta kod tekrarı yapmayı elzem kılıyorsa kullanılması tavsiye edilmektedir.

Faydaları
  • Front Controller, uygulamaya yönelik tüm istekleri işler. Böylece, kullanıcı izleme ve güvenlik gibi genel politikaların uygulanması için merkezi kontrol sağlar.
  • Yeni bir istek geldiğinde Front Controller karşılayacağından dolayı thread safety sağlamaktadır.
  • Uygulamanın konfigürasyonu büyük ölçüde basitleştirilmiştir.

Front Controller Design Pattern, handler sınıflarında aynı kodu tekrar tekrar yazmaksızın yeniden kullanma esnekliği ve yeteneği sağlar. Çünkü yetki, log vs. gibi işlemler Front Controller tarafından gerçekleştirilir.

MVC Modeliyle İlişkileri

Asp.NET(Core) MVC mimarisi tam anlamıyla Front Controller tasarımına uygun bir altyapıyla geliştirilmiştir. İlgili mimarideki Routing yapılanması esasında buradaki Dispatcher nesnesiyle eşdeğer görülebilir ve her ikisi de gelen isteği uygun handler(controller)lara dağıtma görevi görmektedir.

Ayrıca Front Controller, MVC’de ki controller’a alternatiftir diyebiliriz. Misal; Front Controller, tüm istekleri bir controller’da işler. MVC’de ise her isteği uygun controller karşılar ve işler. Haliyle Front Controller MVC’ye nazaran daha karmaşıktır.

Bu durumu daha net şöyle yorumlayabiliriz. MVC genel bir kalıptır. Yani MVC’de fikir, bir uygulamanın işleyişini ModelViewController olarak ayırmaktır. Bu fikir bir uygulamanın özelliklerine göre iki farklı şekilde gerçekleştirilebilir. Birincisi, her bir isteğe yanıt veren ayrı controller sınıfı oluşturmaktır. Yani klasik MVC pattern’ı uygulamaktır. İkicisi ise tüm istekleri karşılayan ve ardından bunları farklı handler’lara yönlendiren tek bir ana controller’a sahip olmak. Yani Front Controller tasarımını uygulamak. Haliyle Front Controller tasarımı MVC kalıbının bir alt taktiği olarak karşımıza çıkmaktadır.

Nihai olarak;
Böylece yerine göre merkezi yapılanmanın gerektiği durumlarda kullanılabilecek bir tasarım olan Front Controller’ı incelemiş ve tasarımsal kavramları eşliğinde meşhur MVC pattern ile olabilecek ilişkilerini değerlendirmiş bulunmaktayız.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir