26.01.2026

CQRS Nedir? Okuma/Yazmayı Ayırarak Hızlanın

CQRS ile okuma ve yazma yükünü ayırın, performans ve ölçeklenebilirliği artırın. Ne zaman gerekli, nasıl uygulanır, örnekle öğrenin.

CQRS Nedir? Okuma/Yazmayı Ayırarak Hızlanın

Meta Description

CQRS nedir? Okuma ve yazmayı ayırarak performansı ve ölçeklenebilirliği artırın. Ne zaman kullanılmalı, nasıl uygulanır, örneklerle öğrenin.

Giriş (Introduction)

Uygulamanız büyüdükçe “listeleme sayfaları yavaş”, “rapor ekranı veritabanını kilitliyor”, “her yeni alan ekleyince bütün sorgular bozuluyor” gibi şikâyetler artar. Özellikle okuma trafiği yazmadan çok fazlaysa (e-ticaret katalogları, dashboard’lar, analytics ekranları) tek bir modelle hem yazma hem okuma yapmak giderek zorlaşır.

İşte burada CQRS (Command Query Responsibility Segregation) devreye girer: Okuma (Query) ve yazma (Command) sorumluluklarını ayırarak hem performansı hem de geliştirme hızını artırmayı hedefler.

Bu yazıda CQRS nedir, hangi senaryolarda gerçekten işe yarar, mimariyi nasıl kurarsınız ve küçük bir örnekle adım adım nasıl ilerlersiniz göreceksiniz.


CQRS Nedir? (Ana Anahtar Kelime)

CQRS, uygulamanızın yazma işlemleri (Command) ile okuma işlemlerini (Query) ayrı modeller, ayrı katmanlar ve çoğu zaman ayrı veri yapılarıyla ele almasıdır.

  • Command (Yazma): Sistemin durumunu değiştirir. Örn: Sipariş oluştur, Adres güncelle.
  • Query (Okuma): Sistemin durumunu sadece okur. Örn: Siparişleri listele, Satış grafiği getir.

Temel fikir: “Yazmak için optimize edilen model” ile “Okumak için optimize edilen model” aynı olmak zorunda değil.

CQRS Event Sourcing mı?

Hayır. CQRS tek başına kullanılabilir. Event Sourcing (durumu event’lerden üretme) CQRS ile sık birlikte anılır ama zorunlu değildir.


Neden CQRS Yapmalıyım?

CQRS’in asıl gücü, “her şeyi tek tablo/tek model ile çözme” yaklaşımının tıkanmaya başladığı yerde ortaya çıkar.

CQRS’in sağladıkları:

  • Performans: Okuma tarafını denormalize/özet tablolarla çok hızlandırabilirsiniz.
  • Ölçeklenebilirlik: Read tarafını ayrı ölçekleyebilirsiniz (ayrı DB, read replica, ayrı cache).
  • Geliştirilebilirlik: Yazma tarafı domain kurallarına odaklanır; okuma tarafı UI/rapor ihtiyaçlarına.
  • Daha güvenli değişiklik: Karmaşık raporlar write modelinizi kirletmez.

Gerçek hayat örneği:

  • E-ticarette ürün listeleme ve filtreleme trafiği çok yüksektir.
  • Sipariş yazma trafiği görece düşüktür ama iş kuralları ağırdır.
  • CQRS ile ürün listelemeyi ayrı bir “read model” üzerinde çok hızlı çalıştırabilir, siparişi ise domain odaklı yönetebilirsiniz.

CQRS Ne Zaman Kullanılmamalı?

Her projeye CQRS eklemek “mimari overkill” olabilir.

CQRS kullanmayın (veya erteleyin) eğer:

  • Uygulama küçük ve CRUD ağırlıklıysa
  • Okuma-yazma oranı dengeliyse ve performans sorunu yoksa
  • Ekip CQRS + eventual consistency mantığını yönetemeyecekse
  • Tek DB ve basit sorgular işinizi görüyorsa

Aşağıdaki tablo hızlı karar vermeye yardımcı olur:

Durum CQRS Mantıklı mı? Neden?
Dashboard/rapor ekranı DB’yi yoruyor Read model ile özetleme/denormalizasyon
Çok yoğun listeleme + filtreleme Okuma tarafını optimize etmek kolaylaşır
Basit admin panel CRUD Ek karmaşıklık gereksiz
Domain kuralları ağır (fintech, sipariş, stok) Command tarafı domain’i temiz tutar
Tek ekip, hızlı MVP ❌/⚠️ Önce basit başlayın, sonra ayırın

CQRS Mimarisi: Read Model ve Write Model Nasıl Ayrılır?

CQRS’de genelde iki farklı “model” konuşulur:

1) Write Model (Command tarafı)

  • Amaç: Doğru iş kuralları ve tutarlılık
  • Yapı: Domain entity’ler, validation, transaction
  • DB: Normalized şema kullanabilir

2) Read Model (Query tarafı)

  • Amaç: Hızlı ve pratik okuma
  • Yapı: Denormalize tablolar, projection’lar, materialized view
  • DB: Farklı DB bile olabilir (PostgreSQL + Elastic gibi)

LSI anahtar kelimeler: read model, write model, projection, denormalizasyon, eventual consistency, materialized view, domain model


Adım Adım CQRS Uygulama (Mini Örnek)

Senaryo: “Sipariş” yazma tarafı domain odaklı; okuma tarafı ise “Sipariş Listeleme” ekranını hızlı döndürmeli.

Adım 1: Command ve Query’yi ayırın

  • CreateOrderCommand
  • GetOrderListQuery

Adım 2: Write tarafında domain kurallarını toplayın

Örnek kurallar:

  • Stok yeterli olmalı
  • Toplam tutar hesaplanmalı
  • Sipariş durumu CREATED başlamalı

Adım 3: Read tarafı için projection tasarlayın

Liste ekranı için ihtiyacınız:

  • orderId, customerName, total, status, createdAt

Bu, write tarafındaki 5 tablo join’iyle hesaplanmasın diye OrderListProjection gibi bir read tablosuna yazılabilir.

Adım 4: Event/Outbox yaklaşımıyla read model’i güncelleyin

Write tarafında sipariş oluşturulduğunda bir “OrderCreated” olayı üretin. Read tarafı bu olayı tüketip projection’ı günceller.

Not: Event Sourcing yapmıyorsanız bile “domain event” + “outbox pattern” ile güvenilir senkronizasyon kurulabilir.

Adım 5: Basit bir pseudo kod örneği

Aşağıdaki örnek, kavramı netleştirmek için sadeleştirilmiş bir Node.js/TypeScript yaklaşımıdır.

// COMMAND
type CreateOrderCommand = {
  customerId: string;
  items: Array<{ productId: string; qty: number }>;
};

async function handleCreateOrder(cmd: CreateOrderCommand) {
  // 1) validation & domain rules
  // 2) write to Orders + OrderItems (transaction)
  const orderId = await writeDb.createOrder(cmd);

  // 3) publish event (idealde outbox ile)
  await eventBus.publish("OrderCreated", { orderId });

  return { orderId };
}

// QUERY
type GetOrderListQuery = { customerId: string; limit: number };

async function handleGetOrderList(q: GetOrderListQuery) {
  // read model: denormalize table
  return readDb.order_list_projection.findMany({
    where: { customerId: q.customerId },
    orderBy: { createdAt: "desc" },
    take: q.limit,
  });
}

// PROJECTION CONSUMER
async function onOrderCreated(evt: { orderId: string }) {
  const order = await writeDb.getOrderWithCustomer(evt.orderId);
  await readDb.order_list_projection.upsert({
    orderId: order.id,
    customerId: order.customerId,
    customerName: order.customerName,
    total: order.total,
    status: order.status,
    createdAt: order.createdAt,
  });
}

Bu yapı sayesinde:

  • Yazma tarafı tutarlılık için optimize olur.
  • Okuma tarafı hız için optimize olur.

En Kritik Konu: Eventual Consistency (Gecikmeli Tutarlılık)

CQRS’de read model çoğu zaman write modelden birkaç milisaniye/saniye geriden gelebilir.

Bunu nasıl yönetirsiniz?

  • UI’da “Siparişiniz alındı, listede görünmesi birkaç saniye sürebilir” gibi mesaj
  • GetOrderById gibi kritik ekranlarda gerektiğinde write DB’den okuma (hibrit yaklaşım)
  • Outbox + retry + idempotent consumer ile kayıp event riskini azaltma

Bunu neden yapmalıyım? Çünkü büyük trafikte her şeyi “anında tutarlı” yapmak pahalıdır. CQRS, okuma performansını artırırken bu maliyeti kontrollü şekilde yönetmenizi sağlar.


CQRS’e Geçiş İçin Pratik Yol Haritası

Sıfırdan büyük CQRS yerine, kademeli geçiş genellikle daha sağlıklıdır:

  1. Önce tek DB ile başlayın
  2. Yavaşlayan ekranı bulun (ör. sipariş listeleme)
  3. Sadece o ekran için read model (projection) oluşturun
  4. Domain event/outbox ile projection’ı besleyin
  5. Trafik artarsa read DB’yi ayrı ölçekleyin

Sık Sorulan Sorular (FAQ)

1) CQRS nedir, REST ile çelişir mi?

Hayır. CQRS bir mimari desendir; REST API ile rahatça uygulanır. Örn: POST /orders (command), GET /orders (query).

2) CQRS için mutlaka iki ayrı veritabanı mı gerekir?

Hayır. Aynı veritabanında ayrı tablolarla (write schema + read projection) başlayabilirsiniz.

3) CQRS performansı her zaman artırır mı?

Doğru problemde evet; ama küçük projede ek karmaşıklık getirir. Özellikle raporlama ve listeleme yoğun sistemlerde fark yaratır.

4) Eventual consistency kullanıcıyı bozar mı?

Doğru UX ile genelde sorun olmaz. Kritik akışlarda hibrit okuma veya “durum ekranı” yaklaşımıyla yönetilir.

5) CQRS ile mikroservis şart mı?

Hayır. Monolith içinde de CQRS uygulanır. Hatta çoğu ekip önce monolith içinde doğru ayrımı yapıp sonra servisleştirir.


Sonuç

CQRS, okuma ve yazma sorumluluklarını ayırarak özellikle okuma trafiği yüksek, raporlaması ağır ve domain kuralları karmaşık sistemlerde ciddi kazanım sağlar. Doğru uygulandığında hem performans hem de sürdürülebilirlik artar; yanlış yerde uygulandığında ise gereksiz karmaşıklık getirir.

Bir sonraki adım olarak şunu deneyin: Uygulamanızda en yavaş “listeleme/rapor” ekranını seçin ve sadece o ekran için küçük bir read model (projection) tasarlayın.

Yorumlarda sisteminizin okuma/yazma oranını ve en çok zorlandığınız ekranı yazın; uygun bir CQRS geçiş stratejisi önerebilirim.