Tizim dizayni kursi · 9-mavzu · DEEP / SENIOR
TuzdiDinMuhammad

CQRS — Senior chuqurlik

Bu — 9-mavzuning 2-qatlami. Core darslikni o‘qib bo‘lgan deb hisoblanadi. Bu yerda eng chuqur narsalar: projection idempotency, read model rebuild/replay, bir nechta projection, mustaqil masshtablash, command concurrency, eventual consistency UX va Event Sourcing bilan birlashtirish — junior’dan seniorgacha.

Daraja: SeniorMavzular: idempotency · replay · concurrency · ESOld shart: 9-mavzu (Core)

Bu qatlamda nima bor

Core darslik asoslarni berdi. Bu Deep qatlam senior darajadagi chuqur mexanikani ochadi: projection idempotency va checkpoint, read model rebuild/replay, bir nechta maxsus projection, o‘qish/yozishni mustaqil masshtablash, command validatsiya va optimistik concurrency, eventual consistency UX naqshlari va Event Sourcing bilan birlashtirish. Oxirida — yig‘ma arxitektura va senior cheklist.

01
Deep · Projection

Idempotency va checkpoint

at-least-once · upsert · checkpoint
🔁

Senior haqiqat: Core’da projection’ni event yangilaydi dedik. Lekin eventlar (3-mavzu) at-least-once yetkaziladi — bir event ikki marta kelishi mumkin. Projection bunga tayyor bo‘lishi shart, aks holda read model buziladi.

Ikki muammo

1. Duplikat: bir event ikki marta qayta ishlansa — masalan, hisoblagich ikki marta oshib ketadi (noto‘g‘ri).
2. Tartib: eventlar tartibi buzilib kelishi mumkin (ayniqsa, ko‘p partition).

Yechim — idempotency + checkpoint

Har projection o‘zining checkpointini (oxirgi qayta ishlangan event pozitsiyasi) yuritadi va allaqachon ko‘rgan eventni tashlab ketadi. Yozish esa upsert (takror ishlansa holat bir xil qoladi):

// IDEMPOTENT PROJECTION: event AT-LEAST-ONCE yetkaziladi (3-mavzu) -> takror kelishi mumkin
@EventsHandler(OrderCreatedEvent)
export class OrderProjection implements IEventHandler<OrderCreatedEvent> {
  async handle(e: OrderCreatedEvent) {
    const cp = await this.getCheckpoint('orderView');
    if (e.position <= cp) return;                  // allaqachon ishlangan -> SKIP

    // upsert (insert YOKI update) -> takror ishlansa ham xavfsiz, holat bir xil
    await this.prisma.orderView.upsert({
      where:  { orderId: e.orderId },
      create: { orderId: e.orderId, userId: e.userId, total: e.total, version: e.version },
      update: { total: e.total, version: e.version },
    });
    await this.saveCheckpoint('orderView', e.position);   // checkpoint'ni oldinga sur
  }
}
event #42 checkpoint = 41 ?42 > 41 -> ishla; aks -> skip upsert + cp=42
Checkpoint duplikat/eski eventni to‘sadi; upsert takrorga chidamli
Nega upsert

Hatto checkpoint bilan ham, "yozdim-u checkpoint saqlashdan oldin yiqildim" holati bor. Upsert (yoki idempotent yozuv) buni xavfsiz qiladi: event qayta kelsa ham, natija o‘zgarmaydi. Idempotency — projection’ning eng muhim sifati.

02
Deep · Rebuild

Read model replay / qayta qurish

replay · blue-green
🏗️

CQRS’ning eng kuchli imkoniyati — read modelni xohlagancha qayta qurish. Read model — shunchaki eventlarning hosilasi; xohlagan paytda o‘chirib, qayta tiklash mumkin.

Nega bu super-kuch

Read model — "haqiqat" emas, balki eventlarning keshlangan ko‘rinishi. Demak:

  • Projection’da bug bo‘lsa — tuzatib, read modelni qayta qurasan (ma’lumot yo‘qolmaydi).
  • Yangi read model shakli kerak bo‘lsa — eski eventlarni qayta o‘ynatib, yangisini to‘ldirasan.
  • Schema o‘zgarsa — yangi sxemaga qayta quriladi.
// REBUILD: read modelni NOLDAN qayta qurish (projection'da bug, yangi shakl, schema)
async rebuildOrderView() {
  await this.prisma.orderView.deleteMany();              // 1) eski read modelni tozala
  await this.resetCheckpoint('orderView');               // 2) checkpoint -> 0
  for await (const event of this.eventStore.readAll()) { // 3) HAMMA eventni qayta o'ynat
    await this.projection.handle(event);                 //    -> read model qaytadan quriladi
  }
}
Blue-green projection (downtime‘siz)

Read modelni jonli almashtirish: yangi versiyasini yon tomonda qurib boshlaysan, u eventlarga yetib olgach (caught up), o‘qishni unga atomik o‘tkazasan, eskisini o‘chirasan. Foydalanuvchi uzilish sezmaydi (5-mavzudagi blue-green deploy g‘oyasi).

Shart

Rebuild faqat eventlar saqlangan bo‘lsa mumkin — Event Sourcing (10-mavzu), yoki event store / Kafka yetarli retention bilan. Eventlar yo‘q bo‘lsa (faqat joriy holat saqlangan) — qayta qurish uchun manba yo‘q.

03
Deep · Multi-model

Bir nechta maxsus projection

polyglot persistence
🎛️

Bitta yozuv modeli — ko‘p read model. Har biri boshqa maqsadga, boshqa omborda optimallashtirilgan. Bu CQRS’ning amaliy kuchi.

Bir xil eventlar oqimidan bir nechta mustaqil projection o‘z read modelini quradi:

Event oqimi(yozuv tomonidan) SQL view (ro‘yxat) Elasticsearch (qidiruv) Redis (hot lookup) Dashboard agregat
Bir event oqimi → ko‘p maxsus read model (polyglot persistence)
Read modelOmborMaqsad
Ro‘yxat / detalSQL view jadvalTez, JOIN’siz sahifalar
QidiruvElasticsearchTo‘liq-matnli, murakkab filtr
Hot lookupRedisEng tez kalit bo‘yicha o‘qish
HisobotlarOldindan hisoblangan agregatDashboard, analitika
Mustaqillik

Har projection o‘z checkpointi, o‘z rebuildiga ega. Bittasi buzilsa — boshqalarga ta’sir qilmaydi. Yangi read model kerak bo‘lsa — istalgan paytda eventlarni qayta o‘ynatib qo‘shasan, yozuv tomoniga tegmasdan.

04
Deep · Scaling

O‘qish/yozishni mustaqil masshtablash

independent scaling
📊

CQRS’ning arxitektura mukofoti: o‘qish va yozish mustaqil masshtablanadi. Ular endi bir-biriga bog‘liq emas — har biri o‘z yukiga qarab o‘sadi.

Real ilovalarda o‘qish yozishdan 10–100 barobar ko‘p. CQRS bilan bu nomutanosiblikni to‘g‘ridan-to‘g‘ri hal qilasan:

Yozuv tomoni (kam) 2 nusxa izchillik · transaksiya single-writer O‘qish tomoni (ko‘p) kesh · replika · sharding — mustaqil
Yozuv izchillik uchun ozchilik; o‘qish throughput uchun ko‘pchilik
  • Yozuv tomoni — izchillik uchun optimallashtiriladi: single-writer, transaksiya, kam nusxa.
  • O‘qish tomoni — throughput uchun: ko‘p nusxa, kesh (4-mavzu), o‘qish replikasi (7-mavzu), hatto alohida sharding (6-mavzu) — yozuv modeliga tegmasdan.
Bog‘lanish

Bu — oldingi mavzularning birlashishi: read modellarini keshlash (4), o‘qish replikasi (7), kerak bo‘lsa sharding (6) — barchasi faqat o‘qish tomonida, yozuv tomonini murakkablashtirmasdan.

05
Deep · Command

Validatsiya va concurrency

optimistic concurrency · idempotency
⚔️

Yozuv tomoni — faqat "saqlash" emas. Senior darajada bu yerda validatsiya qatlamlari va concurrency (parallel o‘zgarishlar) boshqariladi.

Command validatsiya — ikki qatlam

1. Kirish validatsiyasi (DTO darajasi): maydonlar to‘g‘ri turdami, bo‘shmi (class-validator). 2. Biznes invariant (aggregat darajasi): "buyurtmani yopilganidan keyin o‘zgartirib bo‘lmaydi", "balans manfiy bo‘lmasin". Birinchisi shaklni, ikkinchisi qoidalarni tekshiradi.

Optimistic concurrency — yo‘qolgan yangilanish

Lost update muammosi

Ikki command bir buyurtmani bir vaqtda o‘zgartirsa: ikkalasi ham eski holatni o‘qiydi, ikkalasi yozadi — ikkinchisi birinchisini jimgina yo‘q qiladi. Pul/inventar bilan bu halokat.

Yechim — versiya bilan optimistik concurrency: yozishda kutilgan versiya o‘zgarmaganligini tekshir, o‘zgargan bo‘lsa rad et:

// OPTIMISTIC CONCURRENCY: ikki command bir aggregatni o'zgartirsa -> lost update
async execute(cmd: UpdateOrderCommand) {
  const order = await this.repo.findById(cmd.orderId);   // hozirgi version = 5
  order.applyChanges(cmd);                               // domen logikasi + invariant

  // YOZISHDA versiyani tekshir: kutilgan versiya o'zgarmaganda GINA yoz
  const res = await this.prisma.order.updateMany({
    where: { id: cmd.orderId, version: 5 },              // version hali 5 bo'lsa
    data:  { ...order.state, version: 6 },               // 6 ga oshir
  });
  if (res.count === 0)                                   // kimdir oraliqda o'zgartirdi
    throw new ConflictException('Concurrent update — qayta urin');
}
Command idempotency

Mijoz so‘rovni qayta yuborsa (retry, 14-mavzu) — command ikki marta bajarilmasin. Har commandga command_id ber va qayta ishlashdan oldin "bu id bajarilganmi?"ni tekshir (dedup). Bu — yozuv tomonidagi idempotency (projection idempotency’ning yozuv juftligi).

06
Deep · UX

Eventual consistency naqshlari

optimistic UI · versioned read
🪄

Core’da eventual consistency’ni ko‘rdik. Senior darajada uni foydalanuvchi uchun sezilmaydigan qilish — UX naqshlari masalasi.

Foydalanuvchi lag’ni sezmasligi uchun

  • Optimistik UI: foydalanuvchining o‘z amalini darhol ekranda ko‘rsat (server tasdig‘ini kutmasdan), fonda haqiqiy holatga moslashtir.
  • Versioned read: command yangi versiya raqamini qaytaradi; o‘qishda read model shu versiyaga yetgunicha qisqa kut (yoki write model’dan fallback).
  • "Pending" holat: "Buyurtmangiz qayta ishlanmoqda…" — foydalanuvchi nima bo‘layotganini biladi.
  • Push/subscribe: projection yetib olganda WebSocket orqali "tayyor" signali yubor.
// VERSIONED READ: command yangi versiyani qaytaradi; o'qishda read model
// shu versiyaga yetgunicha biroz kutamiz (read-your-writes UX)
async getOrderView(orderId: string, minVersion: number) {
  for (let i = 0; i < 10; i++) {
    const view = await this.prisma.orderView.findUnique({ where: { orderId } });
    if (view && view.version >= minVersion) return view;   // projection yetdi -> ber
    await sleep(50);                                        // biroz kut (max ~500ms)
  }
  return this.readFromWriteModel(orderId);                  // fallback: write model'dan
}
Sinxron projection — kritik yo‘l uchun

Ba’zi joylarda (to‘lov tasdig‘i) eventual consistency joiz emas. Bunda o‘sha bir read modelni sinxron yangilash mumkin (command bilan bitta transaksiyada) — kechikishni boshqa modellarning asinxronligi bilan qoplab. Hammasi async bo‘lishi shart emas.

07
Deep · ES

Event Sourcing bilan birlashtirish

CQRS + ES
🔗

CQRS va Event Sourcing (ES) — tez-tez birga aytiladi, lekin mustaqil. Ularning munosabatini aniq tushunish senior belgisi.

Ular nima va nega mos

Event Sourcing (10-mavzu) — joriy holat o‘rniga barcha eventlarni haqiqat manbai sifatida saqlash. CQRS bilan birlashganda ideal:

  • Yozuv tomoni = event oqimi (ES) — har o‘zgarish event sifatida saqlanadi.
  • O‘qish tomoni = o‘sha eventlardan qurilgan projection’lar.
  • Rebuild (2-bo‘lim) tabiiy bo‘ladi — eventlar allaqachon saqlangan. To‘liq audit/tarix bepul keladi.
Command-> event Event storehaqiqat manbai (ES) Projection'larread modellar (CQRS)
CQRS + ES: event store yozuv haqiqati, projection’lar o‘qish modellari
Lekin — mustaqil

CQRS ES’ni talab qilmaydi: projection’larni oddiy holat-o‘zgarish eventlaridan yoki CDC’dan (7-mavzu logical replication) ham qurish mumkin. ES ham CQRS’ni talab qilmaydi. Ikkalasi birga kuchli, lekin har biri alohida ham ishlatiladi. Ikkalasini birga olish — katta murakkablik, faqat haqiqiy ehtiyojda.

08
Deep · Arxitektura

Yig‘ma ko‘rinish va senior cheklist

To‘liq CQRS arxitekturasi — yig‘ma ko‘rinish

QismVazifaBo‘lim
Command + validatsiya + optimistik concurrencyIzchil yozuv5
Event (yozuv natijasi)O‘qish tomonini xabardor qiladi1
Idempotent projection + checkpointIshonchli read model qurish1
Bir nechta read model (SQL/ES/Redis)Har maqsadga optimal o‘qish3
Rebuild / replayBug tuzatish, yangi shakl, schema2
Eventual consistency UXLag’ni foydalanuvchidan yashirish6

Senior tayyorlik cheklisti

Savol / amaliyotBo‘lim
Projection’ni idempotent + checkpoint bilan quraman (at-least-once’ga chidamli)1
Read modelni replay bilan qayta quraman; blue-green almashtiraman2
Bir event oqimidan bir nechta maxsus read model quraman3
O‘qish va yozishni mustaqil masshtablayman (kesh/replika/sharding o‘qishda)4
Command’da ikki qatlam validatsiya + optimistik concurrency + idempotency qo‘llayman5
Eventual consistency’ni optimistik UI / versioned read / sinxron projection bilan boshqaraman6
CQRS va ES munosabatini bilaman; qachon birga, qachon alohida7

Bu 9-mavzuning Deep/Senior qatlami edi — Core bilan birga, CQRS endi to‘liq.

Keyingi mavzu: “10-mavzu: Event Sourcing” — joriy holat o‘rniga barcha eventlarni haqiqat manbai sifatida saqlash (CQRS’ning tabiiy jufti). Core’dan boshlaymiz.