---
title: 'Komut ve Sorgu Sorumluluğu Ayrımı'
source: 'https://academia.sh/tr/kurslar/alan-odakli-tasarim/komut-ve-sorgu-sorumlulugu-ayrimi'
course: 'Alan Odaklı Tasarım'
language: tr
updated: '2026-08-17T18:10:40+00:00'
license: 'CC BY-SA 4.0'
---

# Komut ve Sorgu Sorumluluğu Ayrımı

Yazma ve okuma modellerinin ayrılması: operasyon panosunun toplama kökleri üzerinden ve ayrı bir okuma modeli üzerinden üretilmesi, üç veri boyunda gezilen nesne sayısının karşılaştırılması, okuma modelinin kaç komut geriden geldiğinin ve kaç yanıtın bayat çıktığının ölçülmesi, bedel olarak eklenen dosya, satır ve eşleme yazımı sayısı.

Buraya kadarki bütün ölçüler yazma yönündeydi: varış kaydının hangi kararlardan geçtiği ve
hangi bağlantı noktalarından dışarı çıktığı. Operasyon masasının asıl işi ise okumaktır: hangi
aktarma noktasında kaç paket bekliyor, bunların kaçı rotasının gerisinde kalmış. Bu soruların
yanıtı toplama kökleri üzerinden aranırsa her yanıt için bütün teslimatlar ve rotalarındaki
bütün ayaklar gezilir; gezinti teslimat sayısıyla büyür.

**Komut ve sorgu sorumluluğu ayrımı** (command query responsibility segregation) bu iki yönü
ayrı modellere verir. Yazma tarafı değişmezleri koruyacak biçimde kurulur — toplama kökleri,
tutarlılık sınırları, kararlar. Okuma tarafı ise sorunun biçimine göre kurulur: sorgunun
istediği satırlar hazır tutulur.

Bu ayrım, Programlama Temelleri kursunda tanıtılan **komut–sorgu ayrımından** farklı bir
şeydir: orada ölçüt bir yöntemin ya durumu değiştirmesi ya değer döndürmesiydi, yani tek bir
yöntemin imzasıydı. Burada ayrılan şey yöntem değil, **model**: aynı veriyi iki ayrı yapıda
tutmak.

## Yazma Modeli

Yazma tarafı önceki derslerin toplama kökünü sürdürür. Her teslimatın rotası, geçtiği noktalar
ve o ana kadar geçmiş olması beklenen nokta sayısı vardır.

```js
// yazma-modeli.mjs — teslimat toplamalari ve tek komut: varis kaydi
const NOKTALAR = ["34", "06", "35", "01", "16"];
const TESLIMATLAR = new Map();

export function tohumla(sayi) {
  TESLIMATLAR.clear();
  let x = 7;
  for (let i = 1; i <= sayi; i += 1) {
    x = (x * 48271 + 11) % 2147483647;
    const uzunluk = 2 + (x % 4);
    const bas = x % NOKTALAR.length;
    const rota = Array.from({ length: uzunluk }, (_, j) => NOKTALAR[(bas + j) % NOKTALAR.length]);
    const ilerleme = x % uzunluk;
    TESLIMATLAR.set(`T${i}`, {
      id: `T${i}`, rota, gecilen: rota.slice(0, ilerleme), beklenen: (x % 2) + ilerleme,
    });
  }
  return TESLIMATLAR.size;
}

export const yazmaModeli = {
  tumu: () => [...TESLIMATLAR.values()].map((t) => structuredClone(t)),
  bul: (id) => structuredClone(TESLIMATLAR.get(id) ?? null),
};

export function varisKomutu(id) {
  const t = TESLIMATLAR.get(id);
  if (t === undefined || t.gecilen.length >= t.rota.length) return null;
  const once = structuredClone(t);
  t.gecilen.push(t.rota[t.gecilen.length]);
  return { once, sonra: structuredClone(t) };
}
```

Tohumlama sabit bir üreteçle yapılıyor; aynı teslimat sayısı her koşumda aynı rotaları verir.
Komut değişimin öncesini ve sonrasını birlikte döndürür, çünkü okuma tarafının hangi satırın
değiştiğini bilmesi gerekir.

## Panonun Yazma Modelinden Üretilmesi

Pano sorgusu her teslimatı okur, rotasını ve geçilen noktalarını gezer, sıradaki noktayı
bulur ve o noktanın satırını arttırır.

```js
// pano-yazma.mjs — pano sorgusu yazma modeli uzerinden: her toplama ve her rota ayagi gezilir
export function panoYazmadan(yazmaModeli) {
  let gezilen = 0;
  const satir = new Map();
  for (const t of yazmaModeli.tumu()) {
    gezilen += 1;
    for (const _ of t.rota) gezilen += 1;
    for (const _ of t.gecilen) gezilen += 1;
    const siradaki = t.rota[t.gecilen.length];
    if (siradaki === undefined) continue;
    const s = satir.get(siradaki) ?? { nokta: siradaki, bekleyen: 0, geride: 0 };
    s.bekleyen += 1;
    if (t.gecilen.length < t.beklenen) s.geride += 1;
    satir.set(siradaki, s);
  }
  return { satirlar: [...satir.values()].sort((a, b) => a.nokta.localeCompare(b.nokta)), gezilen };
}
```

`gezilen` sayacı iddiayı ölçüye çeviriyor: her toplama kökü, her rota ayağı ve her geçilmiş
nokta bir gezinti sayılıyor.

## Okuma Modeli

Okuma modeli beş satırdan oluşur — her aktarma noktası için bir satır. Satırlar bir kez
kurulur, sonra yalnız komutların ürettiği farklarla güncellenir. Uygulanmamış farklar bir
kuyrukta bekler; kuyruğun boyu modelin gecikmesidir.

```js
// okuma-modeli.mjs — pano satirlarini hazir tutan okuma modeli ve uygulanmamis guncelleme kuyrugu
const SATIR = new Map();
const BEKLEYEN = [];

export const okumaModeli = {
  kur(satirlar) {
    SATIR.clear(); BEKLEYEN.length = 0;
    for (const s of satirlar) SATIR.set(s.nokta, { ...s });
  },
  yayinla(delta) { BEKLEYEN.push(delta); },
  gecikme: () => BEKLEYEN.length,
  ilerlet(adet = BEKLEYEN.length) {
    const uygulanan = BEKLEYEN.splice(0, adet);
    for (const delta of uygulanan) {
      for (const [nokta, bekleyen, geride] of delta) {
        const s = SATIR.get(nokta) ?? { nokta, bekleyen: 0, geride: 0 };
        s.bekleyen += bekleyen; s.geride += geride;
        SATIR.set(nokta, s);
      }
    }
    return uygulanan.length;
  },
  pano() {
    let gezilen = 0;
    const satirlar = [];
    for (const s of SATIR.values()) { gezilen += 1; if (s.bekleyen > 0) satirlar.push({ ...s }); }
    return { satirlar: satirlar.sort((a, b) => a.nokta.localeCompare(b.nokta)), gezilen };
  },
};
```

İki modeli birbirine bağlayan tek parça eşlemedir: bir komutun hangi satırları hangi yönde
değiştirdiğini hesaplar.

```js
// izdusum.mjs — bir komutun degistirdigi pano satirlarini hesaplayan esleme
export function izdusum({ once, sonra }) {
  const eski = once.rota[once.gecilen.length], yeni = sonra.rota[sonra.gecilen.length];
  const delta = [];
  if (eski !== undefined) delta.push([eski, -1, once.gecilen.length < once.beklenen ? -1 : 0]);
  if (yeni !== undefined) delta.push([yeni, 1, sonra.gecilen.length < sonra.beklenen ? 1 : 0]);
  return delta;
}
```

## Ölçüm

Ölçüm üç şeyi ayrı ayrı sayar. Birincisi gezinti: üç ayrı teslimat sayısında iki yolun gezdiği
nesne. İkincisi tazelik: yirmi dört komut boyunca okuma modelinin kaç komut geriden geldiği ve
kaç sorgunun yazma modelinden farklı yanıt verdiği. Üçüncüsü bedel: eklenen dosya, satır ve
eşleme yazımı sayısı.

```js
// pano-say.mjs — sorgu icin gezilen nesne, tazelik gecikmesi ve izdusum esleme sayisi
import { readFileSync } from "node:fs";
import { tohumla, yazmaModeli, varisKomutu } from "./yazma-modeli.mjs";
import { panoYazmadan } from "./pano-yazma.mjs";
import { okumaModeli } from "./okuma-modeli.mjs";
import { izdusum } from "./izdusum.mjs";

for (const n of [50, 200, 800]) {
  tohumla(n);
  const y = panoYazmadan(yazmaModeli);
  okumaModeli.kur(y.satirlar);
  const o = okumaModeli.pano();
  const esit = JSON.stringify(y.satirlar) === JSON.stringify(o.satirlar);
  console.log(`teslimat = ${String(n).padStart(3)}  yazma modelinden gezilen = ${String(y.gezilen).padStart(4)}` +
    `, okuma modelinden gezilen = ${o.gezilen}, satirlar esit mi = ${esit}`);
}

tohumla(200);
okumaModeli.kur(panoYazmadan(yazmaModeli).satirlar);
console.log(`baslangic panosu = ${JSON.stringify(okumaModeli.pano().satirlar)}`);

const KOMUTLAR = Array.from({ length: 24 }, (_, i) => `T${i * 7 + 3}`);
const ILERLETME_ARALIGI = 6;
let bayat = 0, enBuyukGecikme = 0, eslemeSayisi = 0, komutSayisi = 0;
for (const [sira, id] of KOMUTLAR.entries()) {
  const sonuc = varisKomutu(id);
  if (sonuc === null) continue;
  komutSayisi += 1;
  const delta = izdusum(sonuc);
  eslemeSayisi += delta.length;
  okumaModeli.yayinla(delta);
  if ((sira + 1) % ILERLETME_ARALIGI === 0) okumaModeli.ilerlet();
  enBuyukGecikme = Math.max(enBuyukGecikme, okumaModeli.gecikme());
  const yazma = JSON.stringify(panoYazmadan(yazmaModeli).satirlar);
  const okuma = JSON.stringify(okumaModeli.pano().satirlar);
  if (yazma !== okuma) bayat += 1;
}
console.log(`komut = ${komutSayisi}, izdusum esleme yazimi = ${eslemeSayisi}`);
console.log(`bayat yanit = ${bayat} / ${komutSayisi}, en buyuk gecikme = ${enBuyukGecikme} komut`);
okumaModeli.ilerlet();
const sonYazma = JSON.stringify(panoYazmadan(yazmaModeli).satirlar);
const sonOkuma = JSON.stringify(okumaModeli.pano().satirlar);
console.log(`kuyruk bosaltildiktan sonra esit mi = ${sonYazma === sonOkuma}, gecikme = ${okumaModeli.gecikme()}`);
console.log(`son pano = ${sonOkuma}`);

const satirSay = (d) => readFileSync(d, "utf8").split("\n").filter((s) => s.trim() !== "").length;
for (const [ad, dosyalar] of [["tek model", ["yazma-modeli.mjs", "pano-yazma.mjs"]],
  ["okuma modeli icin eklenen", ["okuma-modeli.mjs", "izdusum.mjs"]]]) {
  console.log(`${ad.padEnd(26)} ${dosyalar.length} dosya, ${dosyalar.reduce((s, d) => s + satirSay(d), 0)} satir`);
}
```

```sh
node pano-say.mjs
```

```
teslimat =  50  yazma modelinden gezilen =  309, okuma modelinden gezilen = 5, satirlar esit mi = true
teslimat = 200  yazma modelinden gezilen = 1220, okuma modelinden gezilen = 5, satirlar esit mi = true
teslimat = 800  yazma modelinden gezilen = 4621, okuma modelinden gezilen = 5, satirlar esit mi = true
baslangic panosu = [{"nokta":"01","bekleyen":45,"geride":29},{"nokta":"06","bekleyen":46,"geride":25},{"nokta":"16","bekleyen":32,"geride":19},{"nokta":"34","bekleyen":40,"geride":15},{"nokta":"35","bekleyen":37,"geride":24}]
komut = 24, izdusum esleme yazimi = 44
bayat yanit = 20 / 24, en buyuk gecikme = 5 komut
kuyruk bosaltildiktan sonra esit mi = true, gecikme = 0
son pano = [{"nokta":"01","bekleyen":43,"geride":27},{"nokta":"06","bekleyen":39,"geride":18},{"nokta":"16","bekleyen":29,"geride":17},{"nokta":"34","bekleyen":38,"geride":12},{"nokta":"35","bekleyen":47,"geride":23}]
tek model                  2 dosya, 46 satir
okuma modeli icin eklenen  2 dosya, 36 satir
```

## Sayıların Okunması

Gezinti sayıları üç boyda da aynı şeyi söylüyor. Yazma modeli üzerinden pano üretmek 50
teslimatta 309, 200 teslimatta 1220, 800 teslimatta 4621 nesne geziyor; oran teslimat başına
yaklaşık 6'dır, çünkü her teslimatın kendisi, rota ayakları ve geçilmiş noktaları ayrı ayrı
okunuyor. Okuma modeli üzerinden aynı pano her boyda 5 nesne geziyor. Bu sayı teslimat
sayısına değil, **aktarma noktası sayısına** bağlıdır; veri büyürken sabit kalır. Üç boyda da
iki yolun ürettiği satırlar eşit çıktı, yani okuma modeli doğru bir izdüşümdür.

Tazelik sayıları bedeli gösteriyor. Yirmi dört komut boyunca kuyruk her altı komutta bir
boşaltıldı; okuma modeli en fazla 5 komut geriden geldi ve 24 sorgunun 20'si yazma modelinden
farklı yanıt verdi. Bu 20 yanıt yanlış değil, **eski**: bir aktarma noktasında bekleyen paket
sayısı en çok beş komut önceki hâliyle görünüyor. Kuyruk boşaltıldıktan sonra iki model yine
eşitlendi ve gecikme 0'a indi.

Bedel iki dosya ve 36 satır. Buna komut başına eşleme yazımı ekleniyor: 24 komut 44 satır
güncellemesi üretti, yani komut başına ortalama 1,8. Ayrım gezinti sayısını `6n`'den sabit
5'e indirirken yazma yolunu bu kadar ağırlaştırıyor.

## Gecikmenin Alan Kararı Olması

Beş komutluk gecikme teknik bir ayar gibi görünür, ama kararı veren alan uzmanıdır. Aktarma
merkezinin panosunda beş komutluk gerilik sorun değildir: sayaç zaten dakikalar içinde
değişiyor. Aynı gecikme, bir paketin ikinci kez aynı noktaya kaydedilmesini engelleyen kararda
kabul edilemez. Bu yüzden ayrım "her sorgu okuma modelinden" biçiminde uygulanmaz; hangi
sorgunun ne kadar gerilik taşıyabileceği bağlam bağlamdır.

Okuma modeli tazeliğinin genel ölçütleri, geçersizleştirme ve tazelik penceresi Önbellekleme,
Kuyruklar ve Eşzamansız İşleme kursunda ele alındı; burada ölçülen tek şey ayrımın kendi
getirdiği geriliktir.

## Ne Zaman Uygulanmaz

Kazanç iki koşula bağlıdır. Birincisi sorgunun gezinti sayısının veri boyuyla büyümesi:
tek bir teslimatı kimliğiyle okuyan bir sorgu yazma modelinde de 1 nesne geziyor, okuma modeli
ona hiçbir şey kazandırmaz. İkincisi sorgunun biçiminin kararlı olması; sorgu her ay
değişiyorsa eşleme de her ay değişir ve iki dosya sürekli açılır.

Üçüncü sınır tutarlılıktır. Bir alan kuralı sorgunun yanıtına dayanıyorsa — "bir noktada en
çok yüz paket bekleyebilir" gibi — o kural okuma modelinden okunamaz, çünkü okuma modeli
geriden gelir. Böyle bir kural yazma tarafındaki tutarlılık sınırının içinde kalmak zorundadır.

## Özet

- Komut ve sorgu sorumluluğu ayrımı yazma ve okuma için iki ayrı model tutar; komut–sorgu
  ayrımı ise tek bir yöntemin ya durumu değiştirmesini ya değer döndürmesini söyler.
- Pano yazma modeli üzerinden 50, 200 ve 800 teslimatta 309, 1220 ve 4621 nesne gezdi; okuma
  modeli üzerinden üç boyda da 5 nesne gezdi ve satırlar eşit çıktı.
- Okuma modeli en fazla 5 komut geriden geldi; 24 sorgunun 20'si eski yanıt verdi, kuyruk
  boşaltılınca iki model eşitlendi.
- Bedel 2 dosya, 36 satır ve komut başına ortalama 1,8 eşleme yazımıdır; ayrım okuma yolunu
  ucuzlatırken yazma yolunu ağırlaştırır.
- Ne kadar geriliğin kabul edilebilir olduğu alan kararıdır; bir alan kuralı sorgunun yanıtına
  dayanıyorsa o kural yazma tarafındaki tutarlılık sınırında kalmalıdır.

## Sonraki Adım

Okuma modeli komutların ürettiği farklardan kuruldu; farkların kendisi ise uygulandıktan sonra
atıldı. Bu, yazma tarafına bir soru sorulamaz hâle getiriyor: bir teslimatın adresi kaç kez
düzeltildi, hangi noktada kaç kez yeniden deneme yapıldı, dağıtıma çıkış ile teslim arasında
hangi sırayla ne oldu? Kayıt yalnız son durumu tuttuğu için bu soruların yanıtı modelde yok.
Sonraki ders durumu değil olay dizisini saklayan bir kalıcılık biçimini ele alır: durumun
olaylardan yeniden kurulup kurulabildiğini sınar, yeniden kurma için kaç olay okunduğunu sayar
ve anlık görüntü alındığında bu sayının nereye indiğini ölçer.
