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?

KatmanClean Architecture RolüCQRS RolüEvent Sourcing Rolü
DomainSaf iş kurallarıAggregates / EntitiesEvent’leri üretir ve uygular
ApplicationUse Case’leri yönetirCommand & Query Handler’larEvent’leri Outbox’a yazar
InfrastructureTeknik detayları barındırırRead Model DB, Outbox, Message BusEvent Store implementasyonu
PresentationUI / API / ControllerKomut & Sorgu endpoint’leriKullanı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:

IdAggregateIdEventNamePayloadPublished
11234UserCreated{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

KavramAçıklamaUygulama Katmanı
SnapshottingUzun geçmişi kısaltmak için ara durum kaydıInfrastructure
Saga / Process ManagerBirden fazla Aggregate’i koordine ederApplication
IdempotencyAynı event birden fazla işlense bile sonuç değişmezProjection / Infrastructure
Event VersioningEvent şemalarının geriye dönük uyumluluğuDomain / Serializer
Feature FlagsStrateji değişimini konfigürasyona taşırApplication

📈 10️⃣ Ölçeklenebilirlik ve Performans

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

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

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

ÖzellikClean ArchitectureCQRSEvent 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)

KatmanSorumlulukTeknoloji
Domainİş mantığı, Aggregate, Event üretimiPure C#
ApplicationCommand/Query işleyiciler, Outbox yazımıMediatR, FluentValidation
InfrastructureEvent Store, DB, Kafka, Redis, ProjectionEF Core, Redis, Kafka
PresentationAPI, Controller, DTOASP.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 *