JavaScript ve Node.js'te Mediator Deseni: Karmaşık İletişimleri Basitleştirin ve Uygulamanızı Modülerleştirin

Diagram illustrating the Mediator design pattern in JavaScript and Node.js for simplifying complex component interactions and building modular applications

Yazılım dünyasında uygulama bileşenleri arasındaki iletişim, çoğu zaman projelerin karmaşıklığını artıran ve bakımını zorlaştıran temel unsurlardan biridir. Özellikle büyük ölçekli ve etkileşimli uygulamalarda, bir bileşenin birden fazla başka bileşenle doğrudan iletişim kurması, sıkı bağımlılıklar (tight coupling) oluşturarak kodun esnekliğini azaltır. Benim geliştirme tecrübelerimde, bu tür sıkı bağımlılıkların yeni özellik eklemeyi veya mevcut kodu değiştirmeyi ne kadar sancılı hale getirdiğini defalarca deneyimledim. İşte bu noktada Tasarım Desenleri (Design Patterns) devreye giriyor ve karmaşık problemleri zarif çözümlerle basitleştirmemizi sağlıyor.

Daha önce JavaScript ve Node.js'te Tasarım Desenleri başlıklı yazımda Singleton, Factory, Observer ve Strategy gibi temel desenlerden bahsetmiştim. Bu yazıda ise, özellikle bileşenler arası iletişimin yoğun olduğu durumlarda adeta bir kurtarıcı gibi sahneye çıkan bir davranışsal desen olan Mediator Deseni'ne odaklanacağız. JavaScript ve Node.js ekosisteminde bu deseni nasıl uygulayacağımızı, avantajlarını ve hangi senaryolarda kullanılması gerektiğini pratik örneklerle ele alacağım.

Mediator Deseni Nedir ve Ne Amaçla Kullanılır?

Mediator Deseni (Arabulucu Deseni), bir nesne kümesinin birbirleriyle doğrudan konuşmasını engelleyerek, bu nesneler arasındaki iletişimi merkezi bir arabulucu nesne aracılığıyla yöneten davranışsal bir desendir. Bu sayede, her nesne diğer nesnelerin varlığını veya nasıl çalıştığını bilmek zorunda kalmaz, sadece arabulucu ile iletişim kurar. Buna "gevşek bağlantı" (loose coupling) adını veriyoruz.

Mediator Deseni'nin Temel Bileşenleri:

  • Mediator (Arabulucu): Ortak bir arayüz tanımlar. İletişimi soyutlar ve nesneler arası işbirliğini yönetir.
  • ConcreteMediator (Somut Arabulucu): Arabulucu arayüzünü uygular ve iletişim logic'ini içerir. Bağlı bileşenlerin referanslarını tutar ve aralarında mesajları iletir.
  • Colleague (Meslektaş/Bileşen): Arabulucu ile iletişim kuran nesneleri tanımlar. Her bileşen, doğrudan diğer bileşenlerle değil, yalnızca arabulucu ile etkileşime girer.
  • ConcreteColleague (Somut Meslektaş/Bileşen): Bileşen arayüzünü uygulayan somut sınıflardır. Kendi iç logic'lerini içerir ve arabulucuya mesaj gönderip ondan mesaj alırlar.
Diagram illustrating the basic structure and core components of the Mediator design pattern, showing how the Mediator centralizes communication between Colleague objects.

Neden Mediator Deseni Kullanmalıyız?

Uygulamalar büyüdükçe, farklı bileşenler arasında artan bağımlılıklar yönetilemez bir hal alabilir. Örneğin, bir formdaki onlarca input alanının birbirine bağımlı olduğunu düşünün: bir input değiştiğinde diğerini etkiliyor, butonu aktif/pasif yapıyor, başka bir bölümü güncelliyor. Bu senaryo, karmaşık bir "spaghetti code"a dönüşebilir.

Mediator Deseni'nin Avantajları:

  • Gevşek Bağlantı (Loose Coupling): Bileşenler birbirlerini bilmez, doğrudan etkileşime girmezler. Bu, bir bileşeni değiştirdiğinizde diğer bileşenlerin etkilenme olasılığını azaltır.
  • Yeniden Kullanılabilirlik: Bileşenler daha bağımsız hale geldiği için, farklı uygulamalarda veya farklı bağlamlarda daha kolay yeniden kullanılabilirler.
  • Bakım Kolaylığı: İletişim mantığı tek bir yerde (arabulucuda) toplandığı için, değişiklik yapmak veya hata ayıklamak daha kolaydır.
  • Karmaşıklığın Azaltılması: N-çok iletişim yerine, N-1 (tüm bileşenler arabulucuya bağlı) bir iletişim yapısı oluşturur.

Mediator Deseni'nin Dezavantajları:

  • Tek Arıza Noktası: Arabulucu tüm iletişimi yönettiği için, bu nesnede oluşabilecek bir hata tüm sistemi etkileyebilir.
  • Arabulucunun Karmaşıklaşması: Çok fazla bileşen ve iletişim mantığı olduğunda, arabulucunun kendisi "tanrı nesnesi" (God Object) haline gelebilir ve yönetimi zorlaşabilir.
  • Performans Maliyeti: Her iletişim bir arabulucu üzerinden geçtiği için çok ufak bir performans maliyeti olabilir, ancak modern JavaScript motorlarında bu genellikle göz ardı edilebilir.

JavaScript'te Mediator Deseni: UI İletişim Örneği

Basit bir web sayfasında, farklı UI elemanlarının birbirleriyle etkileşime girdiği bir senaryoyu ele alalım: bir input alanı, bir buton ve bir text alanı. Input alanına yazı yazıldığında butonun durumu değişsin ve text alanında bu değişiklik yansıtılsın.

// 1. Mediator Arayüzü (genellikle soyut bir sınıf veya interface) - JS'te direkt uygulanmaz
// Sadece fikir olarak burada belirtilmiştir.
class Mediator {
  notify(sender, event) {
    throw new Error('This method should be overridden!');
  }
}

// 2. ConcreteMediator (Somut Arabulucu)
class ConcreteUIMediator extends Mediator {
  constructor(inputElement, buttonElement, textDisplayElement) {
    super();
    this.input = inputElement;
    this.button = buttonElement;
    this.textDisplay = textDisplayElement;

    // Bileşenlerin arabulucuya referansını ver
    this.input.setMediator(this);
    this.button.setMediator(this);

    // Olay dinleyicilerini ayarla
    this.input.element.addEventListener('input', () => this.notify(this.input, 'inputChanged'));
    this.button.element.addEventListener('click', () => this.notify(this.button, 'buttonClicked'));
  }

  notify(sender, event) {
    if (sender === this.input && event === 'inputChanged') {
      const inputValue = this.input.getValue();
      if (inputValue.length > 0) {
        this.button.enable();
        this.textDisplay.updateText(`Input değeri: ${inputValue}`);
      } else {
        this.button.disable();
        this.textDisplay.updateText('Input boş.');
      }
    } else if (sender === this.button && event === 'buttonClicked') {
      this.textDisplay.updateText(`Butona tıklandı! Input değeri: ${this.input.getValue()}`);
    }
  }
}

// 3. Colleague (Meslektaş/Bileşen) - Soyut sınıf, Mediator'a referans tutar
class UIComponent {
  constructor(element) {
    this.element = element;
    this.mediator = null;
  }

  setMediator(mediator) {
    this.mediator = mediator;
  }
}

// 4. ConcreteColleague (Somut Meslektaş/Bileşen)
class InputComponent extends UIComponent {
  constructor(element) {
    super(element);
  }

  getValue() {
    return this.element.value;
  }
}

class ButtonComponent extends UIComponent {
  constructor(element) {
    super(element);
  }

  enable() {
    this.element.disabled = false;
    this.element.textContent = 'Gönder (Aktif)';
  }

  disable() {
    this.element.disabled = true;
    this.element.textContent = 'Gönder (Pasif)';
  }
}

class TextDisplayComponent extends UIComponent {
  constructor(element) {
    super(element);
  }

  updateText(text) {
    this.element.textContent = text;
  }
}

// Kullanım
const inputElem = document.createElement('input');
inputElem.placeholder = 'Bir şeyler yazın...';

const buttonElem = document.createElement('button');
buttonElem.textContent = 'Gönder';
buttonElem.disabled = true;

const textDisplayElem = document.createElement('p');
textDisplayElem.textContent = 'Input boş.';

document.body.appendChild(inputElem);
document.body.appendChild(buttonElem);
document.body.appendChild(textDisplayElem);

const input = new InputComponent(inputElem);
const button = new ButtonComponent(buttonElem);
const textDisplay = new TextDisplayComponent(textDisplayElem);

// Mediator'ı oluştur ve tüm bileşenleri ona bağla
const mediator = new ConcreteUIMediator(input, button, textDisplay);

Bu örnekte, InputComponent, ButtonComponent ve TextDisplayComponent birbirlerinin varlığından haberdar değil. Sadece ConcreteUIMediator ile konuşuyorlar. ConcreteUIMediator, input'tan gelen değişikliği algılayıp hem butona hem de metin gösterim alanına gerekli güncellemeleri iletiyor. Bu, özellikle React gibi komponent bazlı kütüphanelerde karmaşık props drilling'i veya Context API'nin aşırı kullanımını önlemek için daha yapısal bir alternatif sunabilir, özellikle çok sayıda sibling komponentin etkileşimi gerektiğinde.

Diagram illustrating the Mediator design pattern facilitating communication between frontend UI components in a JavaScript application.

Node.js'te Mediator Deseni: Servis İletişimini Yönetme

Node.js tabanlı mikroservis mimarilerinde veya büyük monolitik uygulamalarda, farklı servis modülleri arasındaki koordinasyon zorlayıcı olabilir. Örneğin, bir sipariş işleme akışında envanter servisi, ödeme servisi ve bildirim servisi gibi birçok birimin birbirine bilgi iletmesi gerekebilir. Doğrudan bağımlılıklar yerine bir arabulucu kullanarak bu iletişimi merkezileştirebiliriz.

Daha önce Node.js ile Ölçeklenebilir Mikroservisler yazımda mikroservislerin bağımsızlığının öneminden bahsetmiştim. Mediator deseni, bu bağımsızlığı korurken, yine de servisler arası koordinasyonu sağlamak için güçlü bir araç olabilir. Hatta mikroservisler arası iletişim stratejilerinde mesaj kuyrukları gibi asenkron çözümlere alternatif veya onlarla birlikte kullanılabilir.

// ServiceMediator.js
class ServiceMediator {
  constructor() {
    this.services = {};
  }

  registerService(name, service) {
    this.services[name] = service;
    service.setMediator(this);
  }

  notify(sender, event, data) {
    // Olayı gönderen servisi ve olay tipini kontrol ederek ilgili servislere yönlendir
    if (sender === 'OrderService') {
      if (event === 'orderCreated') {
        console.log('Mediator: Yeni sipariş oluşturuldu, envanter ve ödeme servislerini bilgilendiriyorum.');
        this.services.InventoryService.updateStock(data.productId, data.quantity);
        this.services.PaymentService.processPayment(data.orderId, data.amount);
      }
    } else if (sender === 'PaymentService') {
      if (event === 'paymentSuccessful') {
        console.log('Mediator: Ödeme başarılı, bildirim ve sipariş servislerini bilgilendiriyorum.');
        this.services.NotificationService.sendNotification(data.orderId, 'Siparişiniz onaylandı!');
        this.services.OrderService.updateOrderStatus(data.orderId, 'paid');
      } else if (event === 'paymentFailed') {
        console.log('Mediator: Ödeme başarısız, sipariş ve bildirim servislerini bilgilendiriyorum.');
        this.services.OrderService.updateOrderStatus(data.orderId, 'failed');
        this.services.NotificationService.sendNotification(data.orderId, 'Ödeme başarısız oldu.');
      }
    }
    // Diğer servislerden gelen olayları burada yönetebilirsiniz.
  }
}

// BaseService.js
class BaseService {
  constructor(name) {
    this.name = name;
    this.mediator = null;
  }

  setMediator(mediator) {
    this.mediator = mediator;
  }

  send(event, data) {
    if (this.mediator) {
      this.mediator.notify(this.name, event, data);
    } else {
      console.warn(`${this.name}: Mediator atanmamış.`);
    }
  }
}

// OrderService.js
class OrderService extends BaseService {
  constructor() {
    super('OrderService');
  }

  createOrder(orderData) {
    console.log(`OrderService: Sipariş oluşturuluyor: ${orderData.orderId}`);
    // Sipariş veritabanına kaydedildiğini varsayalım...
    this.send('orderCreated', orderData);
  }

  updateOrderStatus(orderId, status) {
    console.log(`OrderService: Sipariş ${orderId} durumu ${status} olarak güncellendi.`);
  }
}

// InventoryService.js
class InventoryService extends BaseService {
  constructor() {
    super('InventoryService');
  }

  updateStock(productId, quantity) {
    console.log(`InventoryService: Ürün ${productId} için stok ${quantity} adet güncellendi.`);
  }
}

// PaymentService.js
class PaymentService extends BaseService {
  constructor() {
    super('PaymentService');
  }

  processPayment(orderId, amount) {
    console.log(`PaymentService: Sipariş ${orderId} için ${amount} TL ödeme işleniyor.`);
    const success = Math.random() > 0.5; // Ödeme rastgele başarılı veya başarısız olsun
    if (success) {
      this.send('paymentSuccessful', { orderId, amount });
    } else {
      this.send('paymentFailed', { orderId, amount, reason: 'Yetersiz bakiye' });
    }
  }
}

// NotificationService.js
class NotificationService extends BaseService {
  constructor() {
    super('NotificationService');
  }

  sendNotification(orderId, message) {
    console.log(`NotificationService: Sipariş ${orderId} için bildirim gönderildi: ${message}`);
  }
}

// Uygulama Başlangıcı
const mediator = new ServiceMediator();

const orderService = new OrderService();
const inventoryService = new InventoryService();
const paymentService = new PaymentService();
const notificationService = new NotificationService();

mediator.registerService('OrderService', orderService);
mediator.registerService('InventoryService', inventoryService);
mediator.registerService('PaymentService', paymentService);
mediator.registerService('NotificationService', notificationService);

// Bir sipariş oluşturma akışını başlatalım
orderService.createOrder({ orderId: 'ORD001', productId: 'PROD001', quantity: 2, amount: 250.00 });

Bu Node.js örneğinde, ServiceMediator nesnesi farklı servisler arasındaki tüm olayları ve etkileşimleri yönetiyor. OrderService, sipariş oluşturduğunda doğrudan InventoryService veya PaymentService ile konuşmuyor; sadece mediator.send() metodunu çağırarak bir olay yayınlıyor. Mediator bu olayı yakalayıp hangi servislerin nasıl tepki vermesi gerektiğini biliyor. Bu yapı, servislerin birbirine olan bağımlılıklarını azaltarak daha temiz ve yönetilebilir bir kod tabanı oluşturur.

Mediator ve Observer Desenleri: Farkları Nelerdir?

Mediator deseni, ilk bakışta Observer desenine benzeyebilir çünkü her ikisi de nesneler arası iletişimi kolaylaştırır. Ancak aralarında önemli bir fark vardır:

  • Observer Deseni: Bir-çok ilişkisini tanımlar. Bir "Subject" (konu), kendisini dinleyen "Observer" (gözlemcileri) doğrudan bilgilendirir. Observer'lar birbirleri hakkında bilgi sahibi değildir, sadece Subject'i dinlerler. Subject'in, Observer'ların nasıl tepki vereceğini bilmesine gerek yoktur.
  • Mediator Deseni: Çok-çok ilişkisini ortadan kaldırarak iletişimi tek bir noktaya (Mediator'a) merkezileştirir. Tüm bileşenler, birbirleri yerine sadece Mediator ile konuşur. Mediator ise hangi bileşenin hangi mesajı alması gerektiğini, hangi olaya nasıl tepki verileceğini bilir ve bu koordinasyonu sağlar.

Özetle, Observer deseni olayları yayınlarken, Mediator deseni olayları *koordine eder* ve bileşenler arası davranışları yönetir.

Mediator tasarım deseni illüstrasyonu: Bir hava trafik kontrol kulesi üzerinden iletişim kuran uçaklar, karmaşık etkileşimlerdeki merkezi kontrolü ve Observer deseninden temel farkını vurgular.

Ne Zaman Mediator Deseni Kullanmalıyız?

Mediator deseni her zaman uygulanması gereken bir desen değildir. Özellikle karmaşık senaryolarda parlarken, basit etkileşimler için gereksiz karmaşıklık yaratabilir. İşte kullanmayı düşünebileceğiniz bazı durumlar:

  • Birden fazla bileşen arasında karmaşık, çok yönlü iletişim olduğunda.
  • Uygulamanızdaki bileşenlerin sıkıca bağlı olduğunu ve bu bağımlılıkları azaltmak istediğinizde.
  • Bileşenlerin yeniden kullanılabilirliğini artırmak istediğinizde.
  • Uygulama içinde bir iş akışının (örneğin, sipariş süreci) birden fazla bileşen arasında koordinasyon gerektirdiği durumlarda.
  • Micro-frontend veya microservice mimarilerinde farklı modüller veya servisler arası koordinasyonu merkezi bir noktadan yönetmek istediğinizde.

Sonuç

Mediator Deseni, JavaScript ve Node.js uygulamalarınızda bileşenler arası karmaşık etkileşimleri yönetmek için son derece değerli bir araçtır. Gevşek bağlantı sağlayarak kodunuzu daha modüler, bakımı daha kolay ve esnek hale getirir. Özellikle büyük ve büyümeye meyilli projelerde, bu desenin sunduğu merkezi iletişim yönetimi, geliştirme süreçlerinizi önemli ölçüde hızlandırabilir ve hata olasılığını azaltabilir.

Ancak unutmayın, her tasarım deseninde olduğu gibi, Mediator desenini de bilinçli bir şekilde, doğru soruna uygulamak esastır. Gereksiz yere kullanmak, faydadan çok karmaşıklık getirebilir. Eğer aklınıza takılan sorular olursa veya bu konularda daha derinlemesine bilgi almak isterseniz, bana ismailyagci371@gmail.com adresinden veya sosyal medya kanallarımdan ulaşabilirsiniz. Sağlıklı ve başarılı kodlamalar dilerim!

Orijinal yazı: https://ismailyagci.com/articles/javascript-ve-nodejste-mediator-deseni-karmasik-iletisimleri-basitlestirin-ve-uygulamanizi-modulerlestirin

Yorumlar

Bu blogdaki popüler yayınlar

Node.js ile Ölçeklenebilir Mikroservisler: Adım Adım Bir Mimari Kılavuzu

JavaScript ve Node.js'te Tasarım Desenleri: Uygulamanızı Güçlendirin ve Ölçeklendirin

Anlık Etkileşim: Node.js, WebSockets ve Socket.IO ile Gerçek Zamanlı Uygulama Geliştirme Rehberi