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 → DomainBu 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 / CacheYani 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
UserEmailChangedBu 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.csBu 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#)





