Clean Architecture + CQRS + Event Sourcing

Clean Architecture + CQRS + Event Sourcing

⚙️ Clean Architecture + CQRS + Event Sourcing

Modern Uygulamaların Üç Kutsal Mimarisi

“Bir sistemin kalitesi, değişimi kaldırma kabiliyetiyle ölçülür.”
— Greg Young (CQRS & Event Sourcing’in yaratıcısı)


🧱 1️⃣ Clean Architecture Temeli: Zemin Oluşturma

Clean Architecture, bağımlılık akışını tanımlar:

Infrastructure → Application → Domain
Presentation  → Application → Domain

Bu yapı bağımlılıkları dıştan içe yönlendirir.
Ancak kendi başına veri yönünü tanımlamaz.
İşte tam burada CQRS devreye girer.


🔀 2️⃣ CQRS: Komutlar ve Sorguların Ayrılığı

CQRS (Command and Query Responsibility Segregation) şunu söyler:

“Yazmak (Command) ile okumak (Query) farklı problemlerdir;
bu yüzden aynı modeli paylaşmamalıdırlar.”

📚 Geleneksel yaklaşım:

Service → Repository → Database

⚙️ CQRS yaklaşımı:

Command → Aggregate (Domain) → Event Store / Outbox  
Query → ReadModel → Database / Cache

Yani artık tek bir OrderService yoktur.
Onun yerine iki farklı akış vardır:

  • Komut akışı: Davranışı değiştirir. (CreateOrderCommand)
  • Sorgu akışı: Durumu okur. (GetOrderByIdQuery)

🧩 3️⃣ Event Sourcing: Durumu Olaylarla Saklamak

Event Sourcing, sistemin durumunu klasik “tablolar” yerine geçmiş olayların toplamı olarak saklar.

“Event Store, sistemin zaman makinesidir.”

Örnek:

UserRegistered
UserProfileUpdated
UserEmailChanged

Bu event’leri sırayla uygularsan:

var user = new User();
user.Apply(new UserRegistered(...));
user.Apply(new UserProfileUpdated(...));
user.Apply(new UserEmailChanged(...));

Sonuç: Kullanıcının güncel hali.
Ama aynı zamanda geçmişin tüm versiyonlarına erişebilirsin.


🧠 4️⃣ Üç Mimari Nasıl Birleşir?

Katman Clean Architecture Rolü CQRS Rolü Event Sourcing Rolü
Domain Saf iş kuralları Aggregates / Entities Event’leri üretir ve uygular
Application Use Case’leri yönetir Command & Query Handler’lar Event’leri Outbox’a yazar
Infrastructure Teknik detayları barındırır Read Model DB, Outbox, Message Bus Event Store implementasyonu
Presentation UI / API / Controller Komut & Sorgu endpoint’leri Kullanıcıyla etkileşim noktası

Bu üç mimari aynı hedefe farklı yollardan gider:

  • Clean Architecture: Bağımlılık yönünü temizler
  • CQRS: Veri akışını ayırır
  • Event Sourcing: Durumu izlenebilir hale getirir

Birlikte kullanıldığında:

“Değişim yönetilebilir, geçmiş izlenebilir, kod test edilebilir hale gelir.”


⚙️ 5️⃣ Gerçek Dünya Akışı

Aşağıda modern .NET sistemlerinde sıkça gördüğümüz tam akış modeli yer alıyor:

[API Controller]
    ↓
[Command Handler (Application)]
    ↓
[Aggregate (Domain)]
    ↓
[Domain Events Raised]
    ↓
[Outbox (Infrastructure)]
    ↓
[Event Bus → Kafka / Redis Stream]
    ↓
[Projection Handler → Read Database]
    ↓
[Query Handler (Application)]
    ↓
[Read Model Returned]

Her adımın görevi açık, bağımlılıklar ters:

  • Domain → event üretir
  • Application → event’i kaydeder
  • Infrastructure → event’i taşır
  • Query → yeni durumu gösterir

💾 6️⃣ Outbox Pattern: Mimari Tutkal

Event Sourcing sistemlerinde en kritik problem,
“event’in kaydedilip yayınlanmama riski”dir.

Çözüm: Outbox Pattern
(Transactional Outbox olarak da bilinir.)

🧩 Ne işe yarar?

  • Event, Domain işlemiyle aynı transaction’da Outbox tablosuna yazılır.
  • Background worker (ör. OutboxDispatcher) bu kaydı alır,
    Kafka, RabbitMQ, Redis gibi sisteme gönderir.
  • Event başarılı şekilde yayıldığında Outbox’tan silinir.

📄 Örnek Tablo:

Id AggregateId EventName Payload Published
1 1234 UserCreated {JSON} false

📦 Uygulama

public class UserAggregate : AggregateRoot
{
    public void Register(string email)
    {
        AddEvent(new UserRegisteredEvent(email));
    }
}
public class OutboxWriter : IOutboxWriter
{
    public async Task SaveAsync(DomainEvent evt)
    {
        _context.Outbox.Add(new OutboxMessage(evt));
        await _context.SaveChangesAsync();
    }
}

🧩 7️⃣ Projection (Read Model) Tarafı

Event Sourcing’in “okuma” tarafında klasik bir tablo yerine
projection bulunur: Event’lerin sonucu olarak oluşan,
okuma için optimize edilmiş yapı.

Örnek:

public class UserProjectionHandler :
    IEventHandler<UserRegistered>,
    IEventHandler<UserEmailChanged>
{
    private readonly ReadDbContext _readDb;
    public async Task Handle(UserRegistered e) =>
        await _readDb.Users.AddAsync(new UserReadModel { Id = e.Id, Email = e.Email });

    public async Task Handle(UserEmailChanged e)
    {
        var user = await _readDb.Users.FindAsync(e.Id);
        user.Email = e.NewEmail;
        await _readDb.SaveChangesAsync();
    }
}

Projection’lar idempotent olmalı, yani aynı event birden fazla gelse bile sonuç değişmemeli.


🔀 8️⃣ CQRS + Event Sourcing + Clean Architecture Entegrasyon Örneği

src/
├── Core/
│   ├── Domain/
│   │   ├── Aggregates/
│   │   │   └── QuizAggregate.cs
│   │   ├── Events/
│   │   │   └── QuizStartedEvent.cs
│   │   └── Shared/
│   │       └── Entity.cs
│   ├── Application/
│   │   ├── Commands/
│   │   │   └── StartQuizCommand.cs
│   │   ├── Queries/
│   │   │   └── GetQuizByIdQuery.cs
│   │   ├── Handlers/
│   │   │   ├── StartQuizCommandHandler.cs
│   │   │   └── GetQuizByIdQueryHandler.cs
│   │   └── Interfaces/
│   │       └── IQuizRepository.cs
├── Infrastructure/
│   ├── Persistence/
│   │   ├── EventStoreDbContext.cs
│   │   └── OutboxDispatcher.cs
│   ├── Messaging/
│   │   └── KafkaProducer.cs
│   └── Projections/
│       └── QuizProjectionHandler.cs
├── Presentation/
│   └── Controllers/
│       └── QuizController.cs
└── Shared/
    └── BuildingBlocks/
        ├── Result.cs
        └── IClock.cs

Bu yapı:

  • Clean Architecture’ın bağımlılık yönünü korur.
  • CQRS’in “Command vs Query” ayrımını uygular.
  • Event Sourcing ile sistemin geçmişini saklar.
  • Outbox ile dağıtık güvenliği sağlar.

🧠 9️⃣ Gelişmiş Konseptler

Kavram Açıklama Uygulama Katmanı
Snapshotting Uzun geçmişi kısaltmak için ara durum kaydı Infrastructure
Saga / Process Manager Birden fazla Aggregate’i koordine eder Application
Idempotency Aynı event birden fazla işlense bile sonuç değişmez Projection / Infrastructure
Event Versioning Event şemalarının geriye dönük uyumluluğu Domain / Serializer
Feature Flags Strateji değişimini konfigürasyona taşır Application

📈 10️⃣ Ölçeklenebilirlik ve Performans

Bu üçlünün birleşimi sadece kod kalitesini değil,
sistem ölçeklenebilirliğini de doğrudan etkiler.

Özellik Faydası
Buffered Logging (IBufferedLogger) Async loglama, düşük latency
Outbox + Retry mekanizması Event kaybı olmadan asenkron yayılım
CQRS Query cache Okuma performansını artırır
Snapshot’lar Event replay süresini azaltır
Idempotent projection Network partition durumlarında güvenlik

🧩 11️⃣ Neden Bu Üçlü En Güçlü Kombinasyondur?

Özellik Clean Architecture CQRS Event Sourcing
Bağımlılık Kontrolü ✅ ❌ ❌
Veri Akışı Ayrımı ❌ ✅ ✅
Zaman Tabanlı Durum Yönetimi ❌ ❌ ✅
Test Edilebilirlik ✅ ✅ ✅
Değişime Dayanıklılık ✅ ✅ ✅
Auditability (Geçmiş Takibi) ❌ ❌ ✅

Üçü bir araya geldiğinde:
“Test edilebilir, geçmişi izlenebilir, değişime dayanıklı bir sistem” ortaya çıkar.


🧠 12️⃣ Sonuç: Modern .NET Mimarisi = Clean + CQRS + Event Sourcing

Bu üçlü;

  • Clean Architecture’ın bağımlılık izolasyonunu,
  • CQRS’in veri akışı netliğini,
  • Event Sourcing’in geçmiş izlenebilirliğini birleştirir.

Sonuç:

Zamanla bozulmayan, ölçeklenebilen, değişime uyumlu bir sistem.


🧩 13️⃣ TL;DR (Kısa Özet)

Katman Sorumluluk Teknoloji
Domain İş mantığı, Aggregate, Event üretimi Pure C#
Application Command/Query işleyiciler, Outbox yazımı MediatR, FluentValidation
Infrastructure Event Store, DB, Kafka, Redis, Projection EF Core, Redis, Kafka
Presentation API, Controller, DTO ASP.NET Core, GraphQL

“Event’ler geçmişi anlatır, CQRS akışı düzenler, Clean Architecture sınırları korur.”
— DotNet Senior Developer (C#)


admin

Leave a Reply

Your email address will not be published. Required fields are marked *