Mediator Tasarım Kalıbı

Çok kullanılan Davranışsal Tasarım Kalıplarından(Behavioral Design Pattern) Aracı Tasarım Kalıbı(Mediator Design Pattern) konusunda teorik bilgi verip devamında kod örneğiyle pekiştirmeye çalışacağım. İyi okumalar.

Engin UNAL
Bilişim Hareketi
5 min readFeb 10, 2022

--

Behavioral Pattern’ler özetle nesneler arasındaki iletişimi tasarımlayan ve böylece iletişimin daha esnek yapılarda yapılmasını sağlayan tasarım kalıplarıdır. Mediator Pattern bir Behavioral Design Pattern’dir. Ve adı üstünde nesnelerin iletişiminde bir aracı istendiği durumlarda yardımcı olmaktadır.

Mediator Pattern ile nesnelerin iletişimi ortak bir noktadan sağlanmakta, nesnelerin birbirleriyle doğrudan iletişime girmesi yerine bir aracıyla iletişime girip haberleşmesini tasarımlamaktadır. Şimdi bunu biraz daha açalım.

Neden böyle bir tasarıma ihtiyaç duyarız? Hangi olası soruna çare arıyoruz?

Nesnelerin birbiriyle doğrudan iletişime girmesi durumunda bir nesnenin iletişime girdiği diğer nesneye bağlı olması(referans etmesi vs.) gereksinimini getirir. Bu sürecin zincirleme devam etmesi, tightly-coupled(sıkı-bağlı) bir tasarımın ortaya çıkması gibi istenmeyen fakat kaçınılmaz sonuçların doğmasına neden olacaktır. Zaman içinde nesne sayısı ve bağlantıların karmaşası arttıkça tüm sistemin esnekliği azalır, yönetim zorlaşır, bakım maliyetleri ve süresi artar ve yapılması planlanan değişikliklerin getirdiği riskler artar. Doğal olarak bu istenen bir durum değildir.

Mediator Pattern bu problemi nasıl çözmeyi öneriyor?

Mediator Pattern yukarıda özetlemeye çalıştığım duruma çözüm olarak iletişimin merkezine bir aracı koyar ve tüm iletişim bunun üzerinden gerçekleşir. Böylece nesneler arası loosely-coupled(gevşek-bağlı) bir bağın kullanılmasına imkan tanır. Nesneler iletişim kurmak istediği diğer nesnelerin referanslarını barındırmaz, doğrudan bağlantı kurmaz, aracıyı kullanarak tüm iletişimlerini bu aracı katman üzerinden sağlarlar.

Yazılım tasarımında mimariyi tasarımlayanların önemli hedeflerinden biri de sistemdeki bileşenlerin birbirlerine sıkı sıkıya bağlı olmamaları ve gerektiğinde değiştirilebilmelerini sağlayacak kadar esnek bir yapının kurgulanmasıdır. Bu bakış açısıyla Mediator Pattern, sistemdeki nesnelerin birbirlerine doğrudan bağlanmalarına gerek kalmayacak bir çözümü önerir. Temel amaç sistemdeki nesneler diğer nesnelere doğrudan bağlı olmak yerine bir aracı nesneye bağlı olsunlar ve iletişim bunun üzerinden sağlansın düşüncesidir.

Bu bize ne kazandırır?

Buraya kadar anlatılanların bir cümlelik özeti; Mediator Pattern, nesneleri birbirinden izole ederek nesne grupları arasındaki etkileşimleri yönetmek için kullanılır diyebiliriz. Bunun kazandırdıkları ise nesneler arası bağımlılığın azaltılması, daha kolay bakım ve yönetilebilirlik olarak sıralanabilir.

Artıları

  • Nesneler arası bağımlıklıkları azaltır. Nesneler arası daha az bağımlılık bu nesnelerin daha fazla yerde kullanılmasına imkan tanır.
  • Single Responsibility, controller’ların arka taraftaki servislerle ilgili bilgi sahibi olmasına gerek yoktur, servisleri çağırması için gereken kodlamaları ve referansları azaltarak her controller’ın kendi sorumluluğuna odaklanmasına yardımcı olur.
  • Open/closed Principle, mediator değişiklikleri veya düzenlemleri mevcut kodlarda değişikliğe neden olmaz.

Eksileri

  • Uygulamada “god class” durumuna gelecek kadar önemli bir faktör haline gelebilir.
  • Doğrudan çağırım modellerine göre uygulamanın performansına negatif ektisi olabilir. Kullanım yerine ve amacına göre değişmektedir.

Buraya kadar anlatılanlara bakıldığında sorun çok net ve buna önerilen çözüm ise anlaşılabilir sadelikte ve uygulaması zor olmayan bir yaklaşımda. Şimdi önerilen bu çözümü kod üzerinden inceleyelim. Sık kullanılan örneklerden birisi uçak-kule örneği üzerinden gitmekte bir diğeri ise request-response modelleri ile örneklemekte. Yine aynı konuda Chat uygulaması örneği de çok verilmiş. Daha az kod içermesi için biraz sadeleştirip chat uygulaması örneği ile devam edeceğim.

Chatroom Uygulaması Örneği

Chat odaları eskiden çok popüler olsa da şimdi yerini başka platformalara bıraktı fakat genelde herkesin bildiği anlaşılması kolay bir yapıları var. Bu odalarda katılımcılar öncelikle odaya giriş yapıyor ve oradaki diğer kişilerle iletişime geçiyor-mesajlaşıyor. Buradaki sorun her katılımcı doğrudan diğer katılımcılarla iletişime geçerse(mesaj alma-gönderme)(many-to-many) uygulamadaki karmaşıklığın çok artması gerekir. Oysa her katılımcı göndermek istediği mesajı tek bir noktaya iletip yeni mesajı da oradan alsa çok daha sade ve yönetilmesi daha kolay bir yapı ortaya çıkar. Bu problemi mediator ile çözelim.

Amacımız katılımcıların birbirlerine doğrudan mesaj atmaları yerine chatroom yapısına mesaj atmaları ve gelen mesajları oradan almaları olacak. Bu şekilde her katılımcının diğer katılımcıya ulaşması gereği ortadan kalkacak ve katılımcı nesneleri sadece chatroom sınıfına ulaşacak.

Sisteminizdeki yapı şu şekilde; Participant class’ımız katılımcı bilgisini tutan yapımız ve ChatroomMediator class’ımız ise chat odasının yönetimi yapan class. Öncelikle participant class’ını tanımlayalım.

Görüldüğü üzere mesajlaşma için Send ve Receive metodlarını kullanıyoruz. Mesaj göndermek için Send metodu kullanılıyor ve mesaj geldiğinde buradaki Receive metoduna düşüyor. Mesaj gönderim ve alım işlemlerini kendi üzerinde gerçekleştirmiyor, ChatroomMediator isimli aracı sınıfımıza ulaşıp onun üzerinden gerçekleştiriyor.

Şimdi ChatroomMediator sınıfını da ekleyelim. Öncelikle interface tanımı ve sonrasında implementasyonu sağladığımız ChatroomMediator sınıfını ekleyeceğiz.

ChatroomMediator kendi içerisinde participant listesini tutuyor. Bunu yapmak için her gelen katılımcı için Register metodu çağrılıyor ve listeye eklenmiş oluyor. Eğer mesaj gönderilecekse ChatroomMediator nesnesinin Send metoduna düşüyor ve buradan ilgili participant bulunarak participant’ın receive metodu çağırılıyor ve mesaj karşı tarafa bu şekilde iletiliyor. Uygulamamızı deneyelim.

Jale to Engin: 'Merhaba.'
Engin to Jale: 'Merhaba nasıl gidiyor..'
Ali to Jale: 'Düzeltmeleri yaptım'
Jale to Ali: 'Tamamdır teşekkürler.'

Çıktısı da yukarıdaki gibi olacaktır. Projeyi aşağıdaki github reposuna yükledim.

Request/Handler gibi diğer konulara getirilen çözüm örneklerini de incelemenizi öneririm.

Yukarıda yapılan ortak işlerin bir soyutlama içerisine alınıp çok daha pratik bir şekilde kullanılabilmesine ve uygulanabilmesine olanak sağlayan bir kütüphane olan MediatR kütüphanesi ile devam etmek istiyorum.

MediatR Kütüphanesi ve Örnek Proje

Jimmy Bogard tarafından geliştirilmiş bir kütüphane olan MediatR ile Mediator Pattern implementasyonlarımızı daha kolay uygulayabiliriz. Nuget paket bilgisine buradan ulaşabilirsiniz.

Bu örnekte webapi projesi oluşturup içerisinde calculator controller oluşturacağız. Calulator controller, çarpma ve bölme işlemi yapacak iki metod içerecek. Bu işlemleri yapacak farklı sınıflarımız olacak ve controller katmanı bu sınıflarla ilgilenmeden gelen request bilgisini mediatr nesnesine gönderecek. Yani gelen request’ten hangi handler’ın çağırılacağı görevini mediatr üstlenmiş olacak.

Öncelikle yeni bir webapi projesi açıp projeye mediatr nuget package eklemesini yapalım. Devamında çarpma işleminde kullanılacak request sınıfını(MultiplyRequest) tanımlayalım.

Bu sınıf MediatR kütüphanesindeki IRequest interface’ini uyguladı, dönüş değeri yani response olarak int tipinde dönüş yaptı. Devam ederek handler sınıfımızı da tanımlayalım. Handler sınıfı, gelen request’in işlendiği, sonucun döndürüldüğü ve tüm diğer işlemleri içeren kodları içeren sınıftır. Biz çarpma işlemi yapacağımızdan burada çarpma işlemi içeren kod satırı bulunmakta.

Request ve Handler tanımlarını yaptık. Handler sınıfımız IRequestHandler interface’ini kullandı, Request olarak MultiplyRequest sınıfını aldı, Response olarak ise int tipini döndü.

Şimdi Controller sınıfımızı tanımlayalım. Controller içerisinde MediatR kütüphanesini enjekte ederek kullanacağız. Kodlamanın tamamını yazının sonunda paylaşacağım github linkinden inceleyebilirsiniz, github’daki repo’da bölme işlemi de mevcut. Controller ile devam edelim.

Görüldüğü üzere çarpma işleminin yapıldığı sınıfa doğrudan ulaşmadan mediatr ile tüm işlemi gerçekleştiriyoruz. mediator.Send() ile request nesnesi mediatr’a gönderiliyor ve mediator da ilgili handler’ın çalıştırılmasını sağlıyor. Yazının başlarında paylaştığım gibi böylece loosely-coupled, esnek ve daha okunabilir bir yapı kurulabiliyor. Bu şekilde controller constructor’ına çarpma işlemi sınıfını veya işlem için hangi sınıf gerekliyse bu sınıfları enjekte etmem gerekmiyor, mediatr ile bu sınıflardan üretilen sonuçları alabiliyoruz.

Projenin github linki:

Yazıyı sonlandırmadan önce bir konuya daha değinmek istiyorum. Mediator tasarım kalıbı genellikle CQRS tasarım kalıbıyla birlikte çok sık kullanılıyor. CQRS, okuma ve yazma işlemlerini ayırmaya yardımcı olan bir tasarım kalıbı. Başka bir yazıda CQRS konusuna da değineceğim fakat altını çizmek istediğim ise Mediator ile birlikte CQRS kullanımını gerektiren bir zorunluluk yok, tamamen projedeki ihtiyaçlara ve tasarıma göre değişebilen yaklaşımlar.

Çok fazla detaya boğmadan Mediator tasarım kalıbını özetlemeye çalıştım umarım bir bakış açısı kazandırmıştır. Okuduğunuz için teşekkürler.

Engin ÜNAL

--

--