Yükleniyor

NET Core ile Chain of Responsibility Tasarım Deseni Kullanımı

Blog Kategorileri

.Net Core Mimariler
Tasarım Desenleri
ORM Araçları
API Geliştirme
Web Geliştirme
Veritabanları
30 Aralık 2025 Salı
NET Core ile Chain of Responsibility Tasarım Deseni Kullanımı

Merhaba,

Modern yazılım geliştirme süreçlerinde karşılaştığımız en büyük zorluklardan biri, iş kurallarının (business rules) zamanla karmaşıklaşması ve birbirine bağımlı hale gelmesidir. Genellikle bu kuralları if-else veya switch-case bloklarıyla çözmeye çalışırız. Ancak bu yaklaşım, zamanla projenin genişlemesini zorlaştıran, okunabilirliği düşük ve bir noktadan sonra yönetilemez bir "kod yığınına" dönüşür.

Peki, birbirini takip etmesi gereken bir dizi kontrolü, ana kodu kirletmeden nasıl yönetebiliriz? İşte burada imdadımıza Chain of Responsibility (Sorumluluk Zinciri) tasarım deseni yetişiyor. Bu desen, bir isteğin (request) bir dizi işlemci (handler) tarafından sırayla işlenmesine olanak tanır. Her bir işlemci, isteği ya kendisi işleyip süreci sonlandırır ya da bir sonraki işlemciye devreder.  Bu yazıda, bir bankanın kredi başvuru sürecini örnek alarak daha temiz, modüler ve test edilebilir hale getirebileceğimizi adım adım inceleyeceğim.

Senaryomuz: Kredi Başvuru Motoru

Bir banka API'si yazdığımızı düşünelim. Müşteri kredi başvurusunda bulunduğunda sırasıyla şu kontrollerden geçmeli:

  1. Vatandaşlık Kontrolü: TC Kimlik no geçerli mi?

  2. Kara Liste (Blacklist) Kontrolü: Müşteri bankanın kara listesinde mi?

  3. Finansal Skor Kontrolü: Kredi notu istenen tutarı karşılıyor mu?

Eğer bu adımlardan herhangi biri başarısız olursa, süreç orada durmalı ve kullanıcıya hata dönülmeli.

Temel Yapı ve Model

Öncelikle API projemizde veriyi taşıyacak modelimizi ve sonucumuzu belirleyelim.

  public class LoanApplicationRequest
  {
      public int CustomerId { get; set; }
      public string IdentityNumber { get; set; }
      public int Age { get; set; }
      public decimal RequestedAmount { get; set; } // İstenen Tutar
      public int CreditScore { get; set; } // Kredi Skoru (Örn: Findeks)
  }
 public class ProcessResult
 {
     public bool IsSuccess { get; set; }
     public string Message { get; set; }

     public static ProcessResult Success() => new ProcessResult { IsSuccess = true };
     public static ProcessResult Fail(string message) => new ProcessResult { IsSuccess = false, Message = message };
 }

Modern .NET Core projelerinde Interface kullanımı hayati önem taşır. Zincirin her halkasının uyacağı kontratı belirliyoruz.

 public interface ILoanApplicationHandler
 {
     ILoanApplicationHandler SetNext(ILoanApplicationHandler next);
     ProcessResult Handle(LoanApplicationRequest request);
 }
// Tekrar eden kodları önlemek için base class
  public abstract class LoanApplicationHandler : ILoanApplicationHandler
  { 
      private ILoanApplicationHandler _nextHandler;

      // Zincire bir sonraki halkayı ekleyen metod 
      public ILoanApplicationHandler SetNext(ILoanApplicationHandler next)
      {
          _nextHandler = next;
          return next;
      }

      public virtual ProcessResult Handle(LoanApplicationRequest request)
      {
          if (_nextHandler != null)
          {
              return _nextHandler.Handle(request);
          }
          else
          {
              return ProcessResult.Success();
          }
      }
  }

Bu sınıf, Chain of Responsibility (Sorumluluk Zinciri) tasarım deseninin bir parçasıdır. Amaç, kredi başvuru sürecini adım adım farklı kontrol aşamalarından geçirmek ve tekrar eden kodları merkezi bir yapıda toplamaktır.

SetNext metodu zincire bir sonraki işlem adımını ekler. Handle metodu ise gelen isteği işler; eğer mevcut sınıf isteği sonuçlandıramazsa, isteği zincirdeki bir sonraki sınıfa iletir. Zincirin sonunda başka bir adım kalmazsa ProcessResult.Success() döndürülür. Sınıfın abstract ve virtual yapısı sayesinde, türeyen sınıflar kendi iş kurallarını ekleyerek davranışı özelleştirebilir.

Şimdi iş mantığımızı parçalara ayıralım. Her sınıf sadece tek bir işten sorumludur (Single Responsibility Principle).

Adım 1: Vatandaşlık Kontrolü

 public class IdentityValidationHandler : LoanApplicationHandler
 {
     public override ProcessResult Handle(LoanApplicationRequest request)
     {
         if (request.IdentityNumber.Length != 11)
         {
             // Zinciri kır ve hata dön
             return ProcessResult.Fail("Geçersiz TC Kimlik Numarası.");
         }

         // Her şey yolundaysa bir sonraki halkaya (Base class üzerinden) geç
         return base.Handle(request);
     }
 }

Bu sınıf, kredi başvurusu sürecindeki sorumluluk zincirinin bir halkasıdır ve başvuruda yer alan TC kimlik numarasının geçerli olup olmadığını kontrol eder. Handle metodu çalıştığında, IdentityNumber değerinin 11 haneli olup olmadığını denetler. Eğer numara 11 haneli değilse süreç burada sonlanır ve “Geçersiz TC Kimlik Numarası.” mesajıyla başarısız bir sonuç döndürülür. Numara geçerliyse herhangi bir hata üretmeden işlemi zincirdeki bir sonraki adıma devretmek için base sınıftaki Handle metodunu çağırır. Burada yapılan kontrol yalnızca örnek amaçlıdır; gerçek bir üretim ortamında TC kimlik numarası doğrulaması daha kapsamlı ve güvenli yöntemlerle gerçekleştirilmelidir.

Adım 2: Kara Liste Kontrolü

Burada normalde veritabanına gidilir, ama simülasyon yapıyoruz.

  public class BlacklistCheckHandler : LoanApplicationHandler
  {
      public override ProcessResult Handle(LoanApplicationRequest request)
      {
          var blacklistedIds = new List<int> { 100, 999 }; // Örnek yasaklı ID'ler

          if (blacklistedIds.Contains(request.CustomerId))
          {
              return ProcessResult.Fail("Müşteri kara listede, kredi verilemez.");
          }

          return base.Handle(request);
      }
  }

Bu sınıf müşterinin kara listede olup olmadığını kontrol eder. Handle metodu çalıştığında, örnek amaçlı oluşturulmuş blacklistedIds listesindeki müşteri numaralarıyla başvurudaki CustomerId değerini karşılaştırır. Eğer müşteri bu listede yer alıyorsa işlem burada sonlandırılır ve “Müşteri kara listede, kredi verilemez.” mesajıyla başarısız bir sonuç döndürülür. Müşteri kara listede değilse, kontrolü zincirdeki bir sonraki adıma devretmek için base sınıftaki Handle metodu çağrılır. Gerçek bir uygulamada bu bilginin genellikle bir veri tabanı veya harici bir sistem üzerinden doğrulanması beklenir.

Adım 3: Finansal Skor Kontrolü

public class CreditScoreEvaluationHandler : LoanApplicationHandler
{
    public override ProcessResult Handle(LoanApplicationRequest request)
    {
        // Basit bir kural: Skor 1000'in altındaysa reddet.
        if (request.CreditScore < 1000)
        {
            return ProcessResult.Fail("Kredi puanı yetersiz.");
        }

        // Tutar yüksekse, skor daha da yüksek olmalı
        if (request.RequestedAmount > 50000 && request.CreditScore < 1500)
        {
            return ProcessResult.Fail("Yüksek tutarlı kredi için puanınız yetersiz.");
        }

        return base.Handle(request);
    }
}

Bu sınıf başvuru sahibinin kredi puanını değerlendirir. Handle metodu çalıştığında önce temel bir kontrol yapar ve kredi puanı 1000’in altındaysa başvuruyu hemen reddeder ve “Kredi puanı yetersiz.” mesajını döndürür. Eğer puan bu eşiğin üzerindeyse ikinci bir değerlendirme yapılır: talep edilen kredi tutarı 50.000’in üzerindeyse, bu durumda daha yüksek bir kredi puanı beklendiği için başvuru sahibinin puanının en az 1500 olması gerekir. Bu şart sağlanmıyorsa, süreç durdurulur ve “Yüksek tutarlı kredi için puanınız yetersiz.” mesajıyla başarısız sonuç döndürülür. Tüm kontroller sorunsuz geçilirse, işlem zincirdeki bir sonraki adıma devredilmek üzere base sınıftaki Handle metoduna iletilir.

Servis Katmanı ve Zincirin Kurulması

Controller içinde new IdentityValidationHandler() demek istemeyiz. Bu zinciri kurma işini bir Servis üstlenmelidir. Bu sayede Controller temiz kalır.

 public interface ILoanApplicationService
 {
     ProcessResult ProcessApplication(LoanApplicationRequest application);
 }

public class LoanApplicationService : ILoanApplicationService
{
    public ProcessResult ProcessApplication(LoanApplicationRequest application)
    {
        // 1. Halkaları oluştur
        var identity = new IdentityValidationHandler();
        var blacklist = new BlacklistCheckHandler();
        var creditScore = new CreditScoreEvaluationHandler();

        // 2. Zinciri bağla (Sıralama ÇOK önemlidir)
        // Vatandaşlık -> Kara Liste -> Skor
        identity.SetNext(blacklist)
            .SetNext(creditScore);

        // 3. Zinciri başlat
        // İlk halkaya isteği gönderiyoruz.
        return identity.Handle(application);
    }
}

Not: Gelişmiş senaryolarda bu handler'lar da Dependency Injection (DI) konteynerinden çekilebilir, böylece handler'ların içinde veritabanı servislerini (Repositories) kullanabilirsiniz.

Bu yapı, kredi başvurusu sürecini yöneten bir servis katmanı tanımlar ve sorumluluk zincirinin Controller içinde manuel olarak oluşturulmasını engeller. ILoanApplicationService arayüzü yalnızca tek bir işlev tanımlar: bir kredi başvurusunu işlemek. LoanApplicationService sınıfı bu arayüzü implemente eder ve ProcessApplication metodu içinde zincirde yer alan handler nesnelerini oluşturur.
 
Metot çalıştığında önce üç farklı kontrol adımı için handler nesneleri oluşturulur: kimlik doğrulama, kara liste kontrolü ve kredi puanı değerlendirmesi. Daha sonra bu handler’lar sırayla birbirine bağlanır; kimlik kontrolleri ilk adım, kara liste kontrolleri ikinci adım ve kredi puanı değerlendirmesi üçüncü adım olacak şekilde zincir oluşturulur. Kurulan zincirdeki sıralama kritik önem taşır, çünkü her adımın sonucu bir sonraki adıma geçilip geçilmeyeceğini belirler. Başvuru işleme alınırken istek ilk halkaya, yani IdentityValidationHandler sınıfına gönderilir; eğer bu adım başvuruyu reddetmezse süreç otomatik olarak zincirin geri kalanına aktarılır. Bu yaklaşım sayesinde Controller sınıfı yalnızca servis üzerinden işlem başlatır ve handler’larla doğrudan uğraşmak zorunda kalmaz. 

Web API Controller Entegrasyonu

Son adımda, API endpoint'imizi yazıyoruz. Gördüğünüz gibi Controller ne kadar temiz ve anlaşılır:

  [ApiController]
  [Route("api/[controller]")]
  public class LoanApplicationsController(ILoanApplicationService _service) : ControllerBase
  {
      [HttpPost]
      public IActionResult SubmitLoanApplication(LoanApplicationRequest application)
      {
          var result = _service.ProcessApplication(application);

          if (result.IsSuccess)
          {
              return Ok(new { Message = "Kredi başvurunuz ön onayı aldı!" });
          }
          else
          {
              // İş kuralı hatası (Bad Request)
              return BadRequest(new { Error = result.Message });
          }
      }
  }

Program.cs Ayarı (Dependency Injection)

builder.Services.AddScoped<ILoanApplicationService,LoanApplicationService>();

Neden Bu Yöntem?

  1. Bakım Kolaylığı (Maintainability): İleriki dönemelerde projeye işlemlerin arasına yaş, gelir durumu gibi kontroller eklenmek istenirse tek yapmanız gereken ilgili sınıfları yazmak ve Service içindeki zincire .SetNext() ile oluşan sınıfları eklemektir. Controller'a veya diğer sınıflara dokunmazsınız.

  2. Test Edilebilirlik (Testability): CreditScoreEvaluationHandler gibi tek bir sorumluluğa sahip küçük bir sınıfın Unit Test ile bağımsız olarak test edilmesi, karmaşık ve onlarca koşul içeren bir Controller yapısını test etmekten çok daha pratiktir ve daha sağlıklı sonuç verir.

  3. Okunabilirlik: İş kuralları küçük ve anlamlı parçalara bölündüğü için kod akışı yukarıdan aşağıya mantıklı ve takip edilmesi kolay bir biçimde ilerler.

Dikkat Edilmesi Gerekenler

Bu deseni her yerde kullanmamalıyız. Sadece sıralı ve birbirini kesme ihtimali olan (validation, onay süreçleri, workflow) işlemler için idealdir. Basit bir CRUD işlemi için bu kadar sınıf açmak "Over-engineering" (aşırı mühendislik) olur. 

Eğer bir isteğin kesinlikle tek bir işlemci tarafından işlenmesi gerekiyorsa veya zincir çok uzun olup performans kaybına yol açıyorsa dikkatli olunmalıdır. Zincirin sonunda isteğin sahipsiz kalma (hiçbir halka tarafından işlenmeme) ihtimali her zaman göz önünde bulundurulmalıdır.

Diğer Bloglarımda Görüşmek Üzere 👋