WisarWisar
Dasturlash kitobi/9-QISM — Arxitektura32 daqiqa

9.7-bob: Event-driven architecture, CQRS, Event Sourcing

9-QISM — Arxitektura va ilg'or backend · 7-mavzu


1. Kirish va motivatsiya

Aloqa usullarini 9.6-bob bildik — endi asinxron event'lar atrofida qurilgan uchta kuchli, ilg'or arxitektura naqshini o'rganamiz: Event-Driven Architecture (EDA — voqea-asoslangan), CQRS (o'qish/yozish ajratish), Event Sourcing (holat = event tarixi). Bular — murakkab, masshtablanadigan tizimlarning eng ilg'or naqshlari (bank, savdo platformasi, IoT, audit talab qiladigan tizimlar). Ular bir-biri bilan chambarchas bog'liq (ko'pincha birga ishlatiladi), lekin har biri alohida tushuncha.

Avval ogohlantirish: bular kuchli, lekin murakkab naqshlar — har loyihaga emas. Oddiy CRUD uchun bular over-engineering (9.1: 2.12). Lekin murakkab domen (ko'p qoida, audit, turli o'qish/yozish yuki, "nima bo'lganini bilish") bo'lganda — bu naqshlar muammoni elegant hal qiladi. Senior dasturchi ularni biladi (qachon kerak, qachon emas), va kerakli joyda ishlatadi. Bu bob ularni tushunarli, real misollar bilan o'rgatadi — siz qachon foydali, qachon ortiqcha ekanini bilib olasiz.

Bu bob: Event-Driven Architecture (voqea atrofida — bo'shashgan bog'lanish), event turlari, CQRS (Command/Query ajratish — nega, qachon), o'qish/yozish modeli, Event Sourcing (holat o'rniga event tarixi — chuqur), event store, projection, eventual consistency (chuqur), Outbox pattern (ishonchli event), va uchalasi birga. Bu bob 9.4 (domain event), 9.6 (queue/Kafka), 8.6 (EventEmitter), 8.26 (audit) bilan bog'liq. Bu — ilg'or arxitektura (senior+ bilim).

O'xshatish: bu naqshlar — bank hisobi tarixi. An'anaviy yondashuv (CRUD): hisob balansini saqlaysiz (balans = 500000) — har o'zgarishda ustiga yozasiz (eski yo'qoladi). Event Sourcing: balansni emas, har tranzaksiyani saqlaysiz ("+1M kirim", "-300k chiqim", "-200k chiqim") — balans bularning yig'indisi (500k). Endi siz butun tarixni bilasiz (nima, qachon, qancha — audit, "qaytarib o'ynash"). CQRS: pul qo'yish (yozish — murakkab qoidalar) va balansni ko'rish (o'qish — tez, optimallashgan) — alohida tizimlar (bankda yozish nazorati qattiq, o'qish tez). Event-driven: tranzaksiya bo'lganda — SMS, hisobot, firibgarlik tekshiruvi (har biri mustaqil reaksiya — voqeaga). Bu naqshlar — "holat" emas, "voqealar oqimi" bilan fikrlash.

Nega muhim?

  • Bo'shashgan bog'lanish — event-driven (mustaqil, kengaytiriladigan).
  • Murakkab domen — CQRS/ES (audit, turli yuk, "nima bo'lgan").
  • Ilg'or arxitektura — senior+ bilim (bank, savdo, IoT).
  • Qachon kerak/emas — pragmatik tanlov (over-engineering xavfi).

2. Nazariya — chuqur tushuntirish

2.1. Event-Driven Architecture (EDA)

text
  EDA — komponentlar EVENT (voqea) orqali gaplashadi:
  Event = "nimadir sodir bo'ldi" (o'tgan zamon — OrderCreated, PaymentReceived)

  An'anaviy (komanda — to'g'ridan):
  OrderService  EmailService.send() (to'g'ridan chaqiradi — qattiq bog'liq)

  Event-driven (voqea — bo'shashgan):
  OrderService  emit("OrderCreated")  [EVENT BUS]
                                          ├ EmailService (tinglaydi)
                                          ├ SmsService (tinglaydi)
                                          └ Analytics (tinglaydi)
   OrderService tinglovchilarni BILMAYDI (mustaqil qo'shiladi)

Event-Driven Architecture (EDA): komponentlar event (voqea — "nimadir bo'ldi" — o'tgan zamon — OrderCreated) orqali gaplashadi. An'anaviy: OrderService EmailService'ni to'g'ridan chaqiradi (qattiq bog'liq). Event-driven: OrderService event e'lon qiladi, tinglovchilar (email, SMS, analytics) mustaqil reaksiya qiladi (OrderService ularni bilmaydi). Bu — bo'shashgan bog'lanish (loose coupling — 9.4: domain event, Observer — 9.2: 2.10). Yangi tinglovchi qo'shilsa — OrderService o'zgarmaydi (OCP — 9.1). EDA asosi.

2.2. Event turlari va event bus

text
  EVENT TURLARI:
  - Domain event 9.4-bob: biznes voqea (OrderCreated — domen ichida)
  - Integration event: xizmatlararo (mikroservis — Kafka/RabbitMQ — 9.6)

  EVENT BUS (event'larni tarqatuvchi):
  - In-process: EventEmitter 8.16-bob, NestJS @OnEvent (monolit — 9.5)
  - Distributed: Kafka, RabbitMQ (mikroservis — 9.6)

  EVENT vs COMMAND:
  - Command: "buni qil" (DoSomething — bitta qabul qiluvchi, javob)
  - Event: "bu bo'ldi" (SomethingHappened — ko'p tinglovchi, javobsiz)

Event turlari: domain event (biznes voqea — domen ichida — 9.4), integration event (xizmatlararo — Kafka/RabbitMQ — 9.6). Event bus (tarqatuvchi): in-process (EventEmitter/NestJS @OnEvent — monolit), distributed (Kafka/RabbitMQ — mikroservis). Event vs Command: command — "buni qil" (CreateOrder — bitta qabul qiluvchi, imperativ); event — "bu bo'ldi" (OrderCreated — ko'p tinglovchi, o'tgan zamon). Bu farq muhim (CQRS — 2.4). Event — fakt (o'zgartirib bo'lmaydi).

2.3. EDA afzalliklari va kamchiliklari

text
  AFZALLIK:
   Bo'shashgan bog'lanish (mustaqil — OCP — 9.1)
   Kengaytiriladigan (yangi tinglovchi — eski o'zgarmaydi)
   Chidamli (asinxron — tinglovchi yiqilsa, event navbatda — 9.6)
   Masshtab (parallel tinglovchilar)

  KAMCHILIK:
   Murakkab kuzatuv (oqim ko'rinmaydi — qaysi event qayerga?)
   Eventual consistency (darrov emas — 2.7)
   Debug qiyin (asinxron — stack trace yo'q)
   Event tartibi (kafolat qiyin — distributed)

   Bo'shashgan, kengaytiriladigan, lekin kuzatuvi murakkab

EDA afzallik: bo'shashgan bog'lanish (mustaqil — OCP), kengaytiriladigan (yangi tinglovchi), chidamli (asinxron — 9.6), masshtab. Kamchilik: murakkab kuzatuv (oqim ko'rinmaydi — qaysi event qayerga ketdi), eventual consistency 2.7-bob, debug qiyin (asinxron — stack trace yo'q), event tartibi (distributed'da kafolat qiyin). EDA — kuchli, lekin "sehirli" emas (kuzatuv/debug murakkabligi narx). Tracing (distributed — 10) muhim. Bo'shashganlik foydasi vs kuzatuv narxi.

2.4. CQRS (Command Query Responsibility Segregation)

text
  CQRS — o'qish (Query) va yozish (Command) ni AJRATISH:

  An'anaviy (bir model — CRUD):
  Bir Model  o'qish ham, yozish ham (bir DB, bir struktura)

  CQRS (ikki model):
  COMMAND (yozish)  Write Model  Write DB (normalized, qoidalar — 6.9)
  QUERY (o'qish)    Read Model   Read DB (denormalized, tez — optimallashgan)

  Nega:
  - O'qish va yozish YUKI har xil (o'qish 100x ko'p — alohida masshtab)
  - O'qish va yozish MODELI har xil (yozish — qoida; o'qish — tez ko'rsatish)

CQRS (Command Query Responsibility Segregation): o'qish (Query) va yozish (Command) ni ajratish — alohida model/DB. An'anaviy CRUD: bir model ikkalasiga; CQRS: write model (normalized, biznes qoidalar — 6.9) va read model (denormalized, tez ko'rsatish). Nega: o'qish/yozish yuki har xil (o'qish ko'pincha 100x ko'p — alohida masshtab); modeli har xil (yozish — qoida; o'qish — tez, tayyor ko'rinish — JOIN'siz). Murakkab tizimda (ko'p o'qish, murakkab yozish) foydali. Oddiy CRUD'ga — over-engineering.

2.5. CQRS qanday ishlaydi (read/write model)

text
  CQRS OQIMI:
  1. Command (yozish): "BuyurtmaYarat"  Command Handler  Write DB
      biznes qoidalar, validatsiya, tranzaksiya 6.9-bob
  2. Event: yozishdan keyin event ("OrderCreated")
  3. Projection: event  Read Model yangilanadi (denormalized)
      Read DB: tayyor ko'rinish (JOIN'siz, tez o'qish)
  4. Query (o'qish): Read DB'dan to'g'ridan (tez — hisoblanmagan)

   Read model — EVENTUAL (yozishdan biroz keyin yangilanadi — 2.7)
   Read DB boshqa bo'lishi mumkin (PostgreSQL yozish, Elasticsearch o'qish)

CQRS oqimi: command (yozish) Command Handler Write DB (qoidalar, tranzaksiya — 6.9) event projection (event Read Model yangilanadi — denormalized) query (o'qish) Read DB'dan to'g'ridan (tez — tayyor, JOIN'siz). Read model eventual (yozishdan biroz keyin — 2.7). Read DB boshqa texnologiya bo'lishi mumkin (yozish — PostgreSQL; o'qish — Elasticsearch 8.22 / Redis 8.15 — har biri o'z ishiga optimal). CQRS — o'qish/yozish mustaqil optimallashtirish.

2.6. Event Sourcing (holat = event tarixi)

text
  EVENT SOURCING — holatni emas, EVENT'larni saqlash:

  An'anaviy (state-oriented): joriy holat saqlanadi
  account: { balans: 500000 }   har o'zgarishda ustiga yoziladi (tarix yo'q)

  Event Sourcing: barcha event saqlanadi (event store)
  events: [
    { MoneyDeposited: 1000000 },    har event o'zgarmas (immutable)
    { MoneyWithdrawn: 300000 },
    { MoneyWithdrawn: 200000 },
  ]
   joriy holat = event'lar YIG'INDISI (replay): 1M - 300k - 200k = 500k

   To'liq tarix (audit — 8.26 tabiiy); "qaytarib o'ynash" (replay)
   Vaqt sayohati (har qanday o'tmish holatni tiklash)
   Murakkab (event store, snapshot, replay)

Event Sourcing (holat = event tarixi — eng ilg'or): joriy holatni emas, barcha event'larni saqlash (event store — immutable). Joriy holat = event'lar yig'indisi (replay — qayta o'ynash). Bank misoli: balans emas, har tranzaksiya saqlanadi balans = yig'indi. Afzallik: to'liq tarix (audit tabiiy — 8.26), "qaytarib o'ynash" (replay), vaqt sayohati (har qanday o'tmish holat). Kamchilik: murakkab (event store, snapshot — tezlik, replay mantiq). Audit/moliyaviy/murakkab domen uchun kuchli; oddiy CRUD'ga — ortiqcha.

2.7. Eventual Consistency (chuqur)

text
  EVENTUAL CONSISTENCY — ma'lumot DARROV emas, BIROZ KEYIN izchil:

  Strong consistency (ACID — monolit): yozish  darrov o'qishda ko'rinadi
  Eventual consistency (CQRS/distributed): yozish  read model BIROZ KEYIN

  Misol: buyurtma yaratildi (write)  read model 100ms keyin yangilanadi
   shu 100ms ichida o'qisa, eski ma'lumot (stale)

  YECHIMLAR:
  - Optimistik UI (frontend darrov ko'rsatadi, keyin moslaydi)
  - Versiya/correlation ID (yozish versiyasini kuting)
  - "Yaratildi" javobida ma'lumotni qaytarish (read'siz)

   Biznes eventual'ga TOQAT qilsa — CQRS; aks holda — monolit (ACID)

Eventual Consistency (chuqur — CQRS/distributed narxi): ma'lumot darrov emas, biroz keyin izchil. Strong consistency (ACID — monolit): yozish darrov o'qishda. Eventual: yozish read model biroz keyin (shu oraliqda eski — stale). Yechimlar: optimistik UI (frontend darrov ko'rsatadi), versiya/correlation ID, "yaratildi" javobida ma'lumot. Biznes eventual'ga toqat qilsa (ko'p holat — 1s kechikish muhim emas) CQRS; aks holda (bank balansi darrov) monolit/ACID. Bu — CQRS qabul qilishning asosiy sharti.

2.8. Outbox Pattern (ishonchli event)

text
  MUAMMO: DB'ga yozish + event yuborish — atomik emas!
  - DB'ga saqladi  event yuborishdan oldin yiqildi  event YO'QOLDI
  - yoki event yubordi  DB saqlanmadi  nomutanosib

  OUTBOX PATTERN (ishonchli):
  1. Bir TRANZAKSIYADA: biznes ma'lumot + event "outbox" jadvaliga (6.9 ACID)
  2. Alohida jarayon outbox'ni o'qib, event'ni queue'ga yuboradi
  3. Yuborilgach — outbox'da belgilaydi

   DB va event ATOMIK (ikkalasi yoki hech narsa — yo'qolmaydi)
   "Dual write" muammosi yechimi (DB + queue ikki alohida tizim)

Outbox Pattern (ishonchli event — muhim): muammo — DB'ga yozish + event yuborish atomik emas (DB saqlandi, event yuborishdan oldin yiqildi event yo'qoldi — "dual write" muammosi). Yechim: outbox — bir tranzaksiyada (6.9 ACID) biznes ma'lumot + event "outbox" jadvaliga; alohida jarayon outbox'ni o'qib queue'ga yuboradi. DB va event atomik (ikkalasi yoki hech narsa — event yo'qolmaydi). Bu — event-driven/CQRS'da ishonchlilikning kaliti (ma'lumot event mosligi). Debezium (CDC) — muqobil.

2.9. Uchalasi birga (EDA + CQRS + Event Sourcing)

text
  KO'PINCHA BIRGA ISHLATILADI (lekin alohida ham):

  Event Sourcing (event store)  event'lar manba
         │
         ├ CQRS Write: command  event  event store
         │
         ├ Event'lar  Projection  Read Model (CQRS Query — 2.5)
         │
         └ Event'lar  EDA (boshqa xizmatlar tinglaydi — 2.1)

   Event Sourcing event'larni yaratadi; CQRS o'qish/yozish ajratadi;
    EDA event'larni tarqatadi (uch naqsh — bitta event oqimi atrofida)

   Alohida ham: faqat EDA (event), faqat CQRS (read/write) — mustaqil

Uchalasi birga (ko'pincha — lekin alohida ham): Event Sourcing event'larni yaratadi (manba — 2.6); CQRS o'qish/yozish ajratadi (event projection read model — 2.5); EDA event'larni tarqatadi (boshqa xizmatlar — 2.1). Uchchasi bitta event oqimi atrofida (tabiiy birga). Lekin alohida ham: faqat EDA (event-driven — CQRS'siz), faqat CQRS (read/write — ES'siz). Ehtiyojga qarab tanlash (hammasi shart emas). Boshlash: EDA (oddiy) CQRS (yuk ajralganda) ES (audit/tarix kerak bo'lganda).

2.10. Qachon kerak / qachon emas (pragmatik)

text
  QACHON KERAK:
   EDA: bo'shashgan bog'lanish, ko'p reaksiya (notification, integratsiya)
   CQRS: o'qish/yozish yuki yoki modeli juda farq (murakkab o'qish)
   Event Sourcing: to'liq audit, "nima bo'lgan", vaqt sayohati (bank, moliyaviy)

  QACHON EMAS (over-engineering — 9.1: 2.12):
   Oddiy CRUD (ma'lumot saqlash/o'qish)
   Kichik loyiha; strong consistency majburiy (eventual toqat qilmaydi)
   Jamoa tajribasiz (murakkablik — debug/kuzatuv qiyin)

   EDA — nisbatan oson (boshlash mumkin); CQRS/ES — murakkab (ehtiyot)

Qachon kerak/emas (pragmatik): EDA — bo'shashgan bog'lanish, ko'p reaksiya (notification, integratsiya — nisbatan oson); CQRS — o'qish/yozish yuki/modeli juda farq; Event Sourcing — to'liq audit, "nima bo'lgan", vaqt sayohati (bank). Qachon EMAS: oddiy CRUD, kichik loyiha, strong consistency majburiy, jamoa tajribasiz (over-engineering — 9.1: 2.12). EDA boshlanishi oson; CQRS/ES — murakkab (ehtiyotkorlik). Aksariyat loyiha CRUD bilan ishlaydi (bu naqshlar maxsus ehtiyoj uchun).

2.11. NestJS CQRS moduli

typescript
// NestJS @nestjs/cqrs — Command/Query/Event ajratish
// Command (yozish)
export class CreateOrderCommand { constructor(public dto: any) {} }

@CommandHandler(CreateOrderCommand)
export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
  async execute(command: CreateOrderCommand) {
    const order = await this.repo.save(command.dto);    // write model
    this.eventBus.publish(new OrderCreatedEvent(order.id));   // event (2.2)
  }
}

// Query (o'qish)
@QueryHandler(GetOrdersQuery)
export class GetOrdersHandler implements IQueryHandler<GetOrdersQuery> {
  async execute() { return this.readRepo.find(); }      // read model (tez)
}

// Event handler (projection / EDA)
@EventsHandler(OrderCreatedEvent)
export class OrderCreatedHandler implements IEventHandler<OrderCreatedEvent> {
  async handle(event: OrderCreatedEvent) {
    await this.readModel.yangila(event.orderId);         // projection (read model)
  }
}

NestJS CQRS moduli (@nestjs/cqrs): Command (yozish — @CommandHandler), Query (o'qish — @QueryHandler), Event (@EventsHandler — projection/EDA). CommandBus/QueryBus/EventBus. NestJS CQRS'ni framework darajasida qo'llab-quvvatlaydi (toza ajratish). Lekin bu — CQRS mexanikasi (qaror emas) — faqat kerak bo'lgandagina ishlatiladi (oddiy CRUD'ga — oddiy service yetadi — 8.1). NestJS imkoniyat beradi; siz qachon ishlatishni hal qilasiz.

2.12. Event-driven uch uslub (notification vs state transfer vs sourcing)

text
  "Event-driven" — bitta so'z, LEKIN uch xil uslub (Martin Fowler tasnifi).
  Farqni bilmaslik — arxitektura chalkashligining asosiy sababi:

  1) EVENT NOTIFICATION (voqea bildirishnomasi):
     Event — faqat "signal": { type: "OrderCreated", orderId: "42" }
     Tinglovchi kerakli ma'lumotni O'ZI so'raydi (API call: GET /orders/42)
      Yengil event (kichik), producer/consumer bo'shashgan
      Ko'p qayta so'rov (chatty), consumer producer'ga bog'liq (so'rash uchun)

  2) EVENT-CARRIED STATE TRANSFER (holat tashuvchi event):
     Event — to'liq ma'lumot: { type: "OrderCreated", order: { id, items, summa, ... } }
     Tinglovchi qo'shimcha so'rov QILMAYDI (hammasi event ichida)
      Consumer mustaqil (producer'ga so'ramaydi), so'rov kam, chidamli
      Katta event, ma'lumot dublikat (har consumer nusxa saqlaydi), eskirish xavfi

  3) EVENT SOURCING 2.6-bob:
     Event — HOLAT MANBASI: barcha event saqlanadi  holat = replay
      To'liq tarix, audit, vaqt sayohati
      Eng murakkab (event store, versioning, snapshot)

   1 va 2 — muvozanat: signal yengil, lekin chatty; state transfer og'ir,
    lekin mustaqil. Ko'p tizim ikkalasini aralashtiradi (muhim event — state,
    boshqasi — notification).

Event-driven uch uslub (Martin Fowler tasnifi — chalkashlik oldini oluvchi muhim farq): (1) Event notification — event faqat signal ({ orderId }), tinglovchi ma'lumotni o'zi API orqali so'raydi (yengil, lekin chatty va producer'ga bog'liq); (2) Event-carried state transfer — event to'liq holatni tashiydi ({ order: {...} }), tinglovchi qo'shimcha so'rov qilmaydi (mustaqil, chidamli, lekin katta event va ma'lumot dublikati); (3) Event sourcing 2.6-bob — event holat manbasi (barcha event replay). Ko'p muhandis "event-driven" deganda faqat (1) ni tushunadi va (2)/(3) bilan aralashtiradi — bu uch uslub turli maqsad, turli narx. Amalda ko'pincha aralash: kritik event — state transfer (mikroservis mustaqilligi uchun), oddiylari — notification.

2.13. Domain event vs integration event (chuqur farq)

text
  Ikkalasi ham "event", LEKIN chegara va shartnoma har xil:

  DOMAIN EVENT 9.4-bob:
  - Chegara: BITTA bounded context / service ICHIDA
  - Shakl: boy domen obyekti (Value Object, Entity ID)
  - Shartnoma: ichki (o'zgartirish oson — bir jamoa)
  - Misol: OrderPlaced (Order aggregate ichida — 9.5)
  - Uzatuv: in-process (EventEmitter, @nestjs/cqrs EventBus)

  INTEGRATION EVENT:
  - Chegara: service'lar ORASIDA (tashqi — Kafka/RabbitMQ 9.6)
  - Shakl: TEKIS, primitiv (id, string, number — versiyalanadigan shartnoma)
  - Shartnoma: PUBLIK (o'zgartirish qiyin — ko'p iste'molchi bog'liq)
  - Misol: order.created.v1 (boshqa service tinglaydi)
  - Uzatuv: broker (asinxron, tarmoq orqali)

   QOIDA: domain event'ni TO'G'RIDAN tashqariga chiqarma!
  Domain event  (mapping)  integration event  broker
  Sabab: ichki modelni tashqariga oshkor qilsang — barcha iste'molchi
  ichki o'zgarishingga bog'lanib qoladi (coupling qaytadi).

Domain event vs integration event (ko'p chalkashadigan, lekin tozalik uchun hal qiluvchi farq): domain event — bitta bounded context/service ichida (9.4, 9.5), boy domen obyekti bilan, ichki shartnoma (o'zgartirish oson), in-process uzatiladi (EventBus). Integration event — service'lar orasida 9.6-bob, tekis/primitiv struktura, publik versiyalangan shartnoma (order.created.v1 — o'zgartirish qiyin, ko'p iste'molchi bog'liq), broker orqali uzatiladi. Muhim qoida: domain event'ni to'g'ridan tashqariga chiqarmang — uni integration event'ga map qiling (ichki model tashqariga oshkor bo'lsa, iste'molchilar ichki o'zgarishga bog'lanib qoladi — coupling qaytadi). Bu ajratish — mikroservis mustaqilligining asosi.

2.14. Saga / Process Manager (distributed tranzaksiya)

text
  MUAMMO: distributed tizimda bitta ACID tranzaksiya YO'Q (05-bob, 2PC qimmat).
  Buyurtma = Order + Payment + Inventory (uch service — bir tranzaksiya bo'lmaydi).
  YECHIM: SAGA — mahalliy tranzaksiyalar ketma-ketligi + kompensatsiya (undo).

  IKKI USLUB:

  CHOREOGRAPHY (xoreografiya — markazsiz, event orqali):
  OrderCreated  PaymentService (event tinglaydi)  PaymentCompleted
               InventoryService (tinglaydi)  StockReserved  ...
   Sodda boshlash, markazsiz (bo'shashgan)
   Oqim ko'rinmaydi (kim kimni kutadi?), sikl xavfi, kuzatuv qiyin

  ORCHESTRATION (orkestratsiya — markaziy koordinator):
  Saga Orchestrator: 1) Payment buyur  2) Inventory buyur  3) Ship buyur
  Har qadam muvaffaqiyatsiz  oldingilarni KOMPENSATSIYA (RefundPayment, ReleaseStock)
   Oqim bir joyda ko'rinadi (aniq mantiq), murakkab jarayonga qulay
   Koordinator — markaziy nuqta (murakkablashadi)

   KOMPENSATSIYA — "rollback" emas (event allaqachon bo'lgan) — TESKARI event:
  PaymentCompleted'ni bekor qilmaysiz — RefundIssued qo'shasiz (biznes undo).

Saga / Process Manager (distributed tranzaksiya — 05-bob cross-ref): muammo — service'lar orasida bitta ACID tranzaksiya yo'q (2PC qimmat, mo'rt). Saga — mahalliy tranzaksiyalar ketma-ketligi + xatolikda kompensatsiya (biznes undo). Ikki uslub: choreography (markazsiz — service'lar bir-birining event'ini tinglaydi; sodda, lekin oqim ko'rinmaydi va kuzatuv qiyin) va orchestration (markaziy koordinator qadamlarni boshqaradi; oqim aniq, lekin markaziy nuqta). Kompensatsiya — DB rollback emas (event bo'lib bo'lgan) — teskari biznes event (RefundIssued, StockReleased). @nestjs/cqrs Saga (rxjs oqimi bilan event yangi command) — choreography'ga yaqin. Murakkab jarayonda orchestration afzal.

2.15. Event schema evolution va upcasting (Event Sourcing)

text
  MUAMMO: event'lar MANGU saqlanadi (immutable). Lekin kod o'zgaradi:
  bugun OrderCreated'ga yangi maydon kerak — 2 yil oldingi event'larda u YO'Q.
  Event Sourcing'da eski event'ni O'ZGARTIRIB bo'lmaydi (immutable — 2.2).

  YECHIMLAR:

  1) VERSIYALASH: har event turida version (OrderCreated_v1, v2)
     eventData: { ..., schemaVersion: 2 }

  2) UPCASTING (yuqoriga ko'tarish): o'qishda eski event  yangi shaklga aylantirish
     v1 { total }  upcast  v2 { total, currency: "UZS" }  (default qo'shiladi)
      replay paytida upcaster zanjiri: v1  v2  v3 (joriy)

  3) WEAK SCHEMA: yangi maydon ixtiyoriy (yo'q bo'lsa — default), maydon O'CHIRMA

   QOIDALAR:
  - Yangi maydon — faqat ixtiyoriy (default bilan)
  - Maydon nomini O'ZGARTIRMA / O'CHIRMA (upcaster orqali map qil)
  - Ma'no o'zgarsa — YANGI event turi (eski v1 saqlanadi)

Event schema evolution / upcasting (Event Sourcing'ning eng amaliy qiyinchiligi): event'lar mangu, immutable 2.2-bob — lekin kod vaqt bilan o'zgaradi (yangi maydon kerak, eski event'da yo'q). Uch yechim: (1) versiyalash (schemaVersion har event ichida); (2) upcasting — o'qish/replay paytida eski event yangi shaklga aylantiriladi (v1 v2 v3 upcaster zanjiri — masalan yo'q maydonga default qo'shiladi); (3) weak schema — yangi maydon ixtiyoriy, mavjud maydon o'chirilmaydi. Qoida: maydon nomini o'zgartirmang/o'chirmang (upcaster orqali map qiling), ma'no o'zgarsa — yangi event turi. Bu — Event Sourcing'da "ma'lumot migratsiyasi" o'rnini bosadi (jadval ALTER emas — upcaster).

2.16. Idempotent consumer (event takror kelganda)

text
  MUAMMO: broker'lar odatda "AT-LEAST-ONCE" kafolat beradi 9.6-bob:
  event KAMIDA bir marta, lekin ba'zan IKKI marta keladi (retry, tarmoq).
   PaymentReceived ikki marta  ikki marta pul yechish (XATO!)

  YECHIM: IDEMPOTENT CONSUMER — bir event'ni ikki marta qayta ishlash =
  bir marta qayta ishlash (natija bir xil).

  1) EVENT ID + "qayta ishlangan" jadvali (processed_events):
     - event keldi  id processed_events'da bormi?
     - HA  tashla (allaqachon ishlangan)
     - YO'Q  ishla + id ni SHU tranzaksiyada yoz (atomik)

  2) Tabiiy idempotentlik: UPSERT (INSERT ... ON CONFLICT), SET (nv qo'shish emas)

   8.20 (idempotency) bilan bir tamoyil — bu yerda event iste'molida.
  Distributed EDA'da idempotentlik — MAJBURIY (ixtiyoriy emas).

Idempotent consumer (distributed EDA'da majburiy — 8.20 idempotency cross-ref): broker'lar odatda at-least-once kafolat beradi 9.6-bob — event kamida bir marta, lekin ba'zan takror keladi (retry, tarmoq uzilishi). Takror ishlash xato beradi (PaymentReceived ikki marta ikki marta yechish). Yechim: (1) event ID + processed_events jadvali — event kelganda id oldin ishlanganmi tekshiriladi (ha tashlanadi; yo'q ishlanadi va id shu tranzaksiyada yoziladi — atomik); (2) tabiiy idempotentlikUPSERT / holatni o'rnatish (qo'shish emas). Bu — outbox (2.8, at-least-once yuborish) ning to'ldiruvchisi: outbox event yo'qolmasligini, idempotent consumer takror zarar qilmasligini kafolatlaydi. Ikkalasi birga — ishonchli event yetkazishning to'liq yechimi.

2.17. Event Sourcing va GDPR ("o'chirish huquqi" ziddiyati)

text
  ZIDDIYAT: Event Sourcing — event MANGU, immutable (o'chirmaslik — asos).
  GDPR (va shaxsiy ma'lumot qonunlari) — "o'chirish huquqi" (right to erasure).
   Foydalanuvchi "ma'lumotimni o'chir" desa, lekin event store o'chirmasa — muammo.

  YECHIMLAR:

  1) CRYPTO-SHREDDING (kripto-parchalash — eng keng tarqalgan):
     Shaxsiy ma'lumotni event ichida SHIFRLAB saqlash (har foydalanuvchi — kalit)
     "O'chirish" = KALITNI o'chirish  shifrlangan ma'lumot O'QIB BO'LMAYDI
      Event immutable qoladi, lekin shaxsiy ma'lumot amalda yo'qoladi

  2) Shaxsiy ma'lumotni event'dan TASHQARIDA (alohida jadval — o'chsa bo'ladi)
     Event'da faqat ID; shaxsiy ma'lumot alohida (an'anaviy o'chirish)

   Event Sourcing tanlashdan OLDIN GDPR'ni o'yla (shaxsiy ma'lumot bo'lsa).
  Bu — Event Sourcing'ning jiddiy amaliy kamchiligi 2.6-bob.

Event Sourcing va GDPR (jiddiy amaliy ziddiyat — 2.6 kamchilik davomi): Event Sourcing'ning asosi — event mangu, immutable (o'chirilmaydi); GDPR/shaxsiy ma'lumot qonunlari esa "o'chirish huquqi" talab qiladi. Ziddiyat. Yechimlar: (1) crypto-shredding (eng keng tarqalgan) — shaxsiy ma'lumot event ichida shifrlab saqlanadi (har foydalanuvchi — alohida kalit); "o'chirish" = kalitni o'chirish ma'lumot o'qib bo'lmaydi (event immutable qoladi, ma'lumot amalda yo'qoladi); (2) shaxsiy ma'lumotni event'dan tashqarida saqlash (event'da faqat ID). Shaxsiy ma'lumot bo'lgan tizimda Event Sourcing tanlashdan oldin GDPR'ni hisobga oling — bu murakkablikni sezilarli oshiradi.

2.18. Best practices (EDA/CQRS/ES)

text
   EDA — bo'shashgan bog'lanish (event — domain event 9.4, queue 9.6)
   Event = o'tgan zamon, immutable (fakt — 2.2)
   CQRS — o'qish/yozish juda farq qilganda (oddiy CRUD'ga emas — 2.4, 2.10)
   Event Sourcing — audit/tarix/vaqt sayohati kerak bo'lganda (2.6, 2.10)
   Eventual consistency — biznes toqat qilsa 2.7-bob
   Outbox pattern (ishonchli event — dual write — 2.8)
   Boshla: EDA  CQRS  ES (bosqichma-bosqich — hammasi birdan emas — 2.9)
   Tracing/monitoring (asinxron kuzatuv — 10, 2.3)
   Idempotent consumer (at-least-once — takror event — 2.16)
   Domain event  integration event map (ichki modelni oshkor qilma — 2.13)
   Event versiyalash + upcasting (schema evolution — 2.15)
   Saga: teskari biznes event bilan kompensatsiya (rollback emas — 2.14)
   Event Sourcing + shaxsiy ma'lumot: crypto-shredding (GDPR — 2.17)
   Over-engineering'dan qoch (CRUD yetsa — CRUD — 2.10)

3. Sintaksis — tez ma'lumotnoma

text
EDA 2.1-bob: emit(event)  event bus  ko'p tinglovchi (bo'shashgan)
CQRS 2.4-bob: Command (yozish, write model) | Query (o'qish, read model)
Event Sourcing 2.6-bob: event store (event'lar)  replay  holat
Eventual consistency 2.7-bob: yozish  read model biroz keyin
Outbox 2.8-bob: bir tranzaksiya (ma'lumot + event jadvali)  keyin queue
Uch uslub 2.12-bob: notification (signal) | state transfer (to'liq) | sourcing
Saga 2.14-bob: choreography (markazsiz) | orchestration (koordinator) + kompensatsiya
Upcasting 2.15-bob: eski event  o'qishda yangi shakl (event O'ZGARMAYDI)
Idempotent 2.16-bob: event ID + processed_events (takror = bir marta)

NestJS 2.11-bob: @CommandHandler | @QueryHandler | @EventsHandler

4. Batafsil kod namunalari

Misol 1 — Event-driven (EDA — monolit — 2.1)

typescript
// NestJS EventEmitter 8.16-bob — bo'shashgan bog'lanish
@Injectable()
export class OrderService {
  constructor(private eventEmitter: EventEmitter2) {}

  async yarat(dto: any) {
    const order = await this.repo.save(dto);
    // Event e'lon (tinglovchilarni BILMAYDI — 2.1)
    this.eventEmitter.emit("order.created", new OrderCreatedEvent(order.id, order.userId));
    return order;
  }
}

// Tinglovchilar (mustaqil — OrderService ularni bilmaydi)
@Injectable()
export class NotificationListener {
  @OnEvent("order.created")
  async handle(event: OrderCreatedEvent) { await this.notify.yubor(event.userId); }
}
@Injectable()
export class AnalyticsListener {
  @OnEvent("order.created")
  async handle(event: OrderCreatedEvent) { await this.analytics.yoz(event); }
}
//  Yangi tinglovchi qo'shish — OrderService o'zgarmaydi (OCP — 9.1)

Misol 2 — Event class (immutable — 2.2)

typescript
// Event — o'tgan zamon, o'zgarmas (fakt)
export class OrderCreatedEvent {
  public readonly occurredAt: Date;                   // qachon bo'ldi
  constructor(
    public readonly orderId: string,
    public readonly userId: string,
    public readonly summa: number,
  ) {
    this.occurredAt = new Date();
    Object.freeze(this);                              // immutable (fakt — o'zgarmas)
  }
}
//  Event NOM — o'tgan zamon (OrderCreated, PaymentReceived — "bo'ldi")
// Command NOM — buyruq (CreateOrder, ProcessPayment — "qil")

Misol 3 — CQRS (NestJS — 2.11)

typescript
// === COMMAND (yozish) ===
export class CreateOrderCommand {
  constructor(public readonly userId: string, public readonly items: any[]) {}
}

@CommandHandler(CreateOrderCommand)
export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
  constructor(
    @InjectRepository(Order) private writeRepo: Repository<Order>,
    private eventBus: EventBus,
  ) {}
  async execute(cmd: CreateOrderCommand) {
    // Write model — biznes qoidalar, tranzaksiya (6.9)
    const order = await this.writeRepo.save({ userId: cmd.userId, items: cmd.items });
    this.eventBus.publish(new OrderCreatedEvent(order.id, cmd.userId, order.summa));   // event
    return order.id;
  }
}

// === QUERY (o'qish) ===
export class GetUserOrdersQuery {
  constructor(public readonly userId: string) {}
}

@QueryHandler(GetUserOrdersQuery)
export class GetUserOrdersHandler implements IQueryHandler<GetUserOrdersQuery> {
  constructor(@InjectRepository(OrderReadModel) private readRepo: Repository<OrderReadModel>) {}
  async execute(query: GetUserOrdersQuery) {
    return this.readRepo.find({ where: { userId: query.userId } });   // read model (tez, tayyor)
  }
}

// Controller
@Controller("orders")
export class OrdersController {
  constructor(private commandBus: CommandBus, private queryBus: QueryBus) {}
  @Post() yarat(@Body() dto: any) { return this.commandBus.execute(new CreateOrderCommand(dto.userId, dto.items)); }
  @Get() olish(@Query("userId") userId: string) { return this.queryBus.execute(new GetUserOrdersQuery(userId)); }
}

Misol 4 — Projection (event read model — 2.5)

typescript
// Event  read model yangilash (denormalized — tez o'qish)
@EventsHandler(OrderCreatedEvent)
export class OrderProjection implements IEventHandler<OrderCreatedEvent> {
  constructor(
    @InjectRepository(OrderReadModel) private readRepo: Repository<OrderReadModel>,
    private usersService: UsersService,
  ) {}

  async handle(event: OrderCreatedEvent) {
    // Read model — DENORMALIZED (JOIN'siz, tez o'qish — 2.5)
    const user = await this.usersService.bitta(event.userId);
    await this.readRepo.save({
      orderId: event.orderId,
      userId: event.userId,
      userIsm: user.ism,                              // denormalized (oldindan qo'shilgan)
      summa: event.summa,
      sana: event.occurredAt,
    });
    //  endi o'qishda JOIN kerak emas (tayyor — tez)
  }
}
//  Read model EVENTUAL (event kelganda yangilanadi — 2.7)

Misol 5 — Event Sourcing (event store — 2.6)

typescript
// Event store — barcha event'lar (holat emas — 2.6)
@Entity("event_store")
export class StoredEvent {
  @PrimaryGeneratedColumn("uuid") id: string;
  @Column() aggregateId: string;                      // qaysi obyekt (account)
  @Column() eventType: string;                        // MoneyDeposited, MoneyWithdrawn
  @Column("jsonb") eventData: any;
  @Column() version: number;                          // tartib
  @CreateDateColumn() occurredAt: Date;
}

@Injectable()
export class AccountEventStore {
  // Event qo'shish (immutable — faqat append)
  async append(aggregateId: string, event: any) {
    await this.repo.save({
      aggregateId, eventType: event.constructor.name, eventData: event,
      version: await this.nextVersion(aggregateId),
    });
  }

  // Holatni REPLAY (event'lar yig'indisi — 2.6)
  async getBalance(accountId: string): Promise<number> {
    const events = await this.repo.find({
      where: { aggregateId: accountId }, order: { version: "ASC" },
    });
    return events.reduce((balans, e) => {             // event'larni "o'ynash"
      if (e.eventType === "MoneyDeposited") return balans + e.eventData.miqdor;
      if (e.eventType === "MoneyWithdrawn") return balans - e.eventData.miqdor;
      return balans;
    }, 0);                                            // boshlang'ich 0  yig'indi = balans
  }
}

Misol 6 — Snapshot (Event Sourcing tezligi — 2.6)

typescript
// MUAMMO: 1M event'ni har safar replay — sekin
// YECHIM: snapshot (vaqti-vaqti holatni saqlash)
@Injectable()
export class AccountService {
  async getBalance(accountId: string): Promise<number> {
    // Oxirgi snapshot'dan boshlash (hamma event emas)
    const snapshot = await this.snapshotRepo.findOne({
      where: { accountId }, order: { version: "DESC" },
    });
    const fromVersion = snapshot?.version || 0;
    let balans = snapshot?.balans || 0;

    // Faqat snapshot'dan keyingi event'larni replay (tez)
    const events = await this.eventStore.getEventsAfter(accountId, fromVersion);
    for (const e of events) balans = this.apply(balans, e);
    return balans;
  }
  // Har 100 event — yangi snapshot (cron yoki yozishda)
}
//  Snapshot — Event Sourcing'ni tez qiladi (replay qisqaradi)

Misol 7 — Outbox pattern (ishonchli event — 2.8)

typescript
// DB + event ATOMIK (dual write muammosi yechimi — 2.8)
@Injectable()
export class OrderService {
  async yarat(dto: any) {
    return this.dataSource.transaction(async (manager) => {   // BIR tranzaksiya (ACID — 6.9)
      // 1. Biznes ma'lumot
      const order = await manager.save(Order, dto);
      // 2. Event SHU tranzaksiyada outbox jadvaliga (atomik!)
      await manager.save(OutboxEvent, {
        eventType: "OrderCreated",
        payload: { orderId: order.id },
        status: "pending",
      });
      return order;                                   // ikkalasi yoki hech narsa
    });
  }
}

// Alohida jarayon — outbox'ni queue'ga yuboradi (cron/poll — 8.18)
@Injectable()
export class OutboxPublisher {
  @Interval(5000)
  async publish() {
    const events = await this.outboxRepo.find({ where: { status: "pending" }, take: 100 });
    for (const e of events) {
      await this.queue.emit(e.eventType, e.payload);  // queue'ga (9.6)
      await this.outboxRepo.update(e.id, { status: "published" });   // belgilash
    }
  }
}
//  DB saqlandi  event ham saqlandi (outbox)  yo'qolmaydi (yiqilsa, keyin yuboriladi)

Misol 8 — Eventual consistency boshqaruvi (2.7)

typescript
// Yozishdan keyin darrov javob (read model kutmasdan — 2.7)
@Post("orders")
async yarat(@Body() dto: any) {
  const orderId = await this.commandBus.execute(new CreateOrderCommand(dto.userId, dto.items));
  // Read model hali yangilanmagan bo'lishi mumkin (eventual)
  //  write natijasini darrov qaytarish (read'siz — stale oldini olish)
  return {
    orderId,
    status: "created",
    // Frontend bu ma'lumot bilan ishlaydi (read model keyin yangilanadi)
  };
}
// Frontend: optimistik UI (darrov ko'rsatadi)  read model yangilangach moslaydi
// yoki: orderId bilan poll (read model tayyor bo'lguncha)

Misol 9 — EDA mikroservis (distributed — 2.1, 9.6)

typescript
// Mikroservisda EDA — Kafka event (9.6)
// Order xizmat — event chiqaradi
@Injectable()
export class OrderService {
  constructor(@Inject("KAFKA") private kafka: ClientKafka) {}
  async yarat(dto: any) {
    const order = await this.repo.save(dto);
    this.kafka.emit("order.events", { type: "OrderCreated", orderId: order.id });   // (9.6: 2.6)
    return order;
  }
}

// Boshqa xizmatlar tinglaydi (mustaqil — distributed EDA)
@EventPattern("order.events")
async handle(@Payload() event: any) {
  if (event.type === "OrderCreated") {
    await this.inventoryService.zaxiraKamaytir(event.orderId);   // mustaqil reaksiya
  }
}
//  Monolitda EventEmitter (Misol 1); mikroservisda Kafka (bo'shashgan — 9.6)

Misol 10 — Qaror (qachon qaysi naqsh — 2.10)

text
  ARXITEKTURA NAQSHI QARORI:

  Loyiha — oddiy CRUD?
  └─ HA  oddiy service 8.1-bob — CQRS/ES/EDA KERAK EMAS (over-engineering)

  Ko'p reaksiya kerak (notification, integratsiya, mustaqil qism)?
  └─ HA  EDA (event-driven — domain event — 9.4, queue — 9.6)

  O'qish/yozish yuki yoki modeli juda farqmi?
  └─ HA  CQRS (read/write ajratish)

  To'liq audit, "nima bo'lgan", vaqt sayohati kerakmi (bank, moliyaviy)?
  └─ HA  Event Sourcing (event store)

  Biznes eventual consistency'ga toqat qiladimi?
  ├─ HA  CQRS/EDA mumkin
  └─ YO'Q (strong consistency majburiy)  monolit/ACID (6.9)

   Boshla oddiy (CRUD/EDA)  murakkab (CQRS/ES) kerak bo'lganda

Misol 11 — Idempotent consumer (event takror kelganda — 2.16)

typescript
// At-least-once broker 9.6-bob  event ba'zan IKKI marta keladi  idempotentlik shart
@Injectable()
export class PaymentConsumer {
  constructor(private dataSource: DataSource) {}

  @EventPattern("payment.received")
  async handle(@Payload() event: { eventId: string; orderId: string; summa: number }) {
    await this.dataSource.transaction(async (manager) => {
      // 1. Bu event ID allaqachon ishlanganmi? (processed_events jadvali)
      const already = await manager.findOne(ProcessedEvent, { where: { eventId: event.eventId } });
      if (already) return;                              // takror — tashla (idempotent)

      // 2. Biznes ishi + event ID belgisi — BIR tranzaksiyada (atomik — 2.8)
      await manager.update(Order, { id: event.orderId }, { status: "paid" });
      await manager.save(ProcessedEvent, { eventId: event.eventId });
    });
  }
}
//  Ikki marta kelsa ham — natija bir xil (bir marta "paid"). Distributed EDA'da MAJBURIY.

Misol 12 — Event upcasting (schema evolution — 2.15)

typescript
// Eski event'ni O'ZGARTIRMAY, o'qishda yangi shaklga ko'tarish (upcast — 2.15)
// v1: { total }      v2: { total, currency }
type UpcasterFn = (data: any) => any;

const upcasters: Record<string, Record<number, UpcasterFn>> = {
  OrderCreated: {
    1: (d) => ({ ...d, currency: "UZS" }),            // v1  v2: default currency qo'shildi
    2: (d) => ({ ...d, source: "web" }),              // v2  v3: default source qo'shildi
  },
};

// Replay paytida event'ni joriy versiyaga ko'tarish (zanjir: v1v2v3)
function upcast(eventType: string, data: any, fromVersion: number, toVersion: number): any {
  let result = data;
  for (let v = fromVersion; v < toVersion; v++) {
    const fn = upcasters[eventType]?.[v];
    if (fn) result = fn(result);                       // har bosqichda default to'ldiriladi
  }
  return result;
}
//  Event store O'ZGARMAYDI (immutable — 2.2); upcaster faqat O'QISHDA (replay) ishlaydi.
// Bu — Event Sourcing'da "jadval migratsiyasi" o'rnini bosadi (ALTER emas — upcaster).

Misol 13 — Saga orchestration (distributed tranzaksiya — 2.14)

typescript
// Markaziy koordinator: qadamlar + xatolikda kompensatsiya (biznes undo — 2.14)
@Injectable()
export class CreateOrderSaga {
  constructor(
    private payment: PaymentService,
    private inventory: InventoryService,
  ) {}

  async run(order: Order) {
    const bajarilgan: string[] = [];                   // kompensatsiya uchun kuzatuv
    try {
      await this.payment.charge(order);   bajarilgan.push("payment");   // 1-qadam
      await this.inventory.reserve(order); bajarilgan.push("inventory"); // 2-qadam
      // ... barcha qadam muvaffaqiyatli  saga tugadi
    } catch (err) {
      // Xatolik  BAJARILGAN qadamlarni TESKARI tartibda kompensatsiya (rollback emas — 2.14)
      if (bajarilgan.includes("inventory")) await this.inventory.release(order);  // StockReleased
      if (bajarilgan.includes("payment"))   await this.payment.refund(order);     // RefundIssued
      throw err;
    }
  }
}
//  Kompensatsiya — DB rollback emas (qadam bo'lib bo'lgan) — TESKARI biznes event.
// Choreography muqobili: har service event tinglaydi (markazsiz — 2.14).

5. To'g'ri va noto'g'ri holatlar

1) Oddiy CRUD'ga CQRS/ES

text
 oddiy ma'lumotga event sourcing (over-engineering — 2.10)
 oddiy service (CRUD)

2) Event'ni o'zgartirish

text
 saqlangan event'ni tahrirlash (fakt emas — 2.2)
 immutable (yangi event qo'shish)

3) Dual write (DB + event alohida)

text
 DB saqla + event yubor (atomik emas — yo'qolishi — 2.8)
 Outbox pattern (bir tranzaksiya)

4) Eventual'ni e'tiborsiz

text
 yozgach darrov read'dan o'qish (stale — 2.7)
 write natijasi / optimistik UI / poll

5) Strong consistency'ga eventual

text
 bank balansiga eventual (xato — 2.7)
 strong consistency (ACID — monolit)

6) Event takrorini e'tiborsiz

text
 consumer takror event'ni qayta ishlaydi (ikki marta pul — 2.16)
 idempotent consumer (event ID + processed_events — Misol 11)

7) Domain event'ni to'g'ridan tashqariga chiqarish

text
 ichki domain event broker'ga (iste'molchi ichki modelga bog'lanadi — 2.13)
 integration event'ga map (publik, versiyalangan shartnoma)

8) Kompensatsiyani DB rollback deb tushunish

text
 saga xatosida "rollback" (event bo'lib bo'lgan — 2.14)
 teskari biznes event (RefundIssued, StockReleased)

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Over-engineering (oddiy loyiha)

Sababi: CQRS/ES har joyga 2.10-bob. Yechimi: CRUD yetsa — CRUD; naqsh — maxsus ehtiyoj.

Xato 2 — Event yo'qoldi

Sababi: dual write 2.8-bob. Yechimi: Outbox pattern.

Xato 3 — Stale read (eski ma'lumot)

Sababi: eventual consistency e'tiborsiz 2.7-bob. Yechimi: write natijasi qaytarish / poll.

Xato 4 — Event Sourcing sekin

Sababi: har safar to'liq replay 2.6-bob. Yechimi: snapshot (Misol 6).

Xato 5 — Event tartibi buzilgan

Sababi: distributed 2.3-bob. Yechimi: partition kaliti (Kafka — tartib), version.

Xato 6 — Debug qiyin (asinxron)

Sababi: oqim ko'rinmaydi 2.3-bob. Yechimi: distributed tracing (correlation ID — 10).

Xato 7 — Event ikki marta ishlandi

Sababi: at-least-once broker, idempotentlik yo'q 2.16-bob. Yechimi: idempotent consumer (event ID + processed_events — Misol 11).

Xato 8 — Eski event kod bilan mos kelmaydi

Sababi: schema o'zgardi, event mangu 2.15-bob. Yechimi: versiyalash + upcasting (Misol 12).

Xato 9 — Shaxsiy ma'lumotni Event Sourcing'da o'chirib bo'lmaydi

Sababi: event immutable, GDPR o'chirish talab qiladi 2.17-bob. Yechimi: crypto-shredding (kalitni o'chirish) yoki shaxsiy ma'lumotni event'dan tashqarida saqlash.

Xato 10 — Saga oqimini kuzatib bo'lmaydi

Sababi: choreography'da markaz yo'q 2.14-bob. Yechimi: murakkab jarayonga orchestration (markaziy koordinator — Misol 13) + tracing.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • Domain event 9.4-bob: EDA asosi.
  • Queue/Kafka 9.6-bob: event tarqatish.
  • EventEmitter 8.16-bob: in-process event.
  • NestJS CQRS 2.11-bob: Command/Query/Event.
  • Audit 8.26-bob: Event Sourcing tabiiy.
  • DB 6.9-bob: outbox tranzaksiya.
  • Tracing (10): asinxron kuzatuv.
  • Qidiruv 8.22-bob: read model (Elasticsearch).
  • Idempotency 8.20-bob: idempotent consumer 2.16-bob.
  • Saga (05-bob): distributed tranzaksiya 2.14-bob.

8. Eng yaxshi amaliyotlar (best practices)

  • EDA — bo'shashgan bog'lanish (event — 9.4, 9.6).
  • Event = o'tgan zamon, immutable (fakt — 2.2).
  • CQRS — o'qish/yozish juda farq (oddiy CRUD'ga emas — 2.4, 2.10).
  • Event Sourcing — audit/tarix/vaqt sayohati (2.6, 2.10).
  • Eventual consistency — biznes toqat qilsa 2.7-bob.
  • Outbox pattern (ishonchli event — 2.8).
  • Boshla EDA CQRS ES (bosqichma-bosqich — 2.9).
  • Snapshot (Event Sourcing tezligi — Misol 6).
  • Tracing (asinxron kuzatuv — 2.3).
  • Idempotent consumer (at-least-once — distributed EDA'da majburiy — 2.16).
  • Domain event'ni integration event'ga map (ichki modelni oshkor qilmaslik — 2.13).
  • Event versiyalash + upcasting (schema evolution — immutable event — 2.15).
  • Saga kompensatsiyasi — teskari biznes event (rollback emas — 2.14).
  • Event Sourcing'da GDPR — crypto-shredding 2.17-bob.
  • Over-engineering'dan qoch (CRUD yetsa — 2.10).

9. Amaliy loyiha: "Event-Driven Bank Tizimi"

EDA/CQRS/Event Sourcing'ni amalda mustahkamlash.

Maqsad

Bank hisobi tizimi: Event Sourcing (tranzaksiya tarixi), CQRS (balans o'qish), EDA (bildirishnoma).

Talablar (requirements)

  1. EDA: event + tinglovchilar (Misol 1, 2.1).
  2. Event class: immutable (Misol 2, 2.2).
  3. CQRS: command/query (Misol 3, 2.11).
  4. Projection: event read model (Misol 4, 2.5).
  5. Event Sourcing: event store + replay (Misol 5, 2.6).
  6. Snapshot: tezlik (Misol 6).
  7. Outbox: ishonchli event (Misol 7, 2.8).
  8. Eventual consistency: boshqaruv (Misol 8, 2.7).
  9. Mikroservis EDA: Kafka (Misol 9, 9.6).
  10. Idempotent consumer: takror event (Misol 11, 2.16).
  11. Saga: ikki hisob orasida o'tkazma + kompensatsiya (Misol 13, 2.14).
  12. Qaror: qachon qaysi naqsh (Misol 10, 2.10).

Maslahatlar (hint)

  • Event immutable (2.2, 2-holat).
  • Outbox (atomik — 2.8, 2-xato).
  • Eventual consistency (2.7, 3-xato).
  • Snapshot (2.6, 4-xato).
  • Strong consistency bank balansiga (2.7, 5-holat).
  • Idempotent consumer (takror event — 2.16, 6-xato).
  • Saga kompensatsiyasi — teskari biznes event, rollback emas (2.14, 8-holat).
  • Over-engineering'dan qoch (2.10, 1-xato).

"Tayyor" mezonlari (acceptance criteria)

  • EDA (event + tinglovchi).
  • Event class (immutable).
  • CQRS (command/query).
  • Projection.
  • Event Sourcing (replay).
  • Snapshot.
  • Outbox.
  • Eventual consistency.
  • Mikroservis EDA.
  • Idempotent consumer.
  • Saga (o'tkazma + kompensatsiya).
  • Qaror.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda ilg'or arxitektura naqshlarini chuqur o'rgandik:

  • Event-Driven Architecture (voqea — bo'shashgan — 2.1); event turlari/bus 2.2-bob; afzallik/kamchilik 2.3-bob; uch uslub (notification vs state transfer vs sourcing — 2.12); domain vs integration event 2.13-bob.
  • CQRS (o'qish/yozish ajratish — 2.4, 2.5); Event Sourcing (holat = event tarixi — 2.6); eventual consistency 2.7-bob; Outbox (ishonchli — 2.8).
  • Saga/process manager (distributed tranzaksiya — choreography vs orchestration — 2.14); schema evolution/upcasting 2.15-bob; idempotent consumer 2.16-bob; Event Sourcing va GDPR 2.17-bob.
  • Uchalasi birga 2.9-bob; qachon kerak/emas (pragmatik — 2.10); NestJS CQRS 2.11-bob.

Keyingi bob — 9.8: API Gateway va service discovery. Event'larni bildik; endi mikroservis arxitekturasining ikki muhim infratuzilma qismini — API Gateway (yagona kirish — 8.16 davomi, BFF) va service discovery (xizmatlar bir-birini topish) — chuqur o'rganamiz.


Foydalanilgan rasmiy/ishonchli manbalar

  • NestJS rasmiy hujjatlari — CQRS moduli (@nestjs/cqrs): CommandBus, QueryBus, EventBus, Saga; mikroservis va event-based aloqa (transport).
  • Microsoft Learn — Azure Architecture Center — CQRS pattern, Event Sourcing pattern, Saga pattern, Transactional Outbox pattern, Idempotent Consumer.
  • Martin Fowler — "What do you mean by Event-Driven?" (event notification vs event-carried state transfer vs event sourcing), CQRS, Event Sourcing maqolalari.
  • Chris Richardson — Microservices Patterns (kitob va patterns to'plami) — Event Sourcing, CQRS, Saga (choreography/orchestration), Transactional Outbox, Idempotent Consumer.
  • Apache Kafka rasmiy hujjatlari — event streaming, delivery semantics (at-least-once), partition va tartib kafolati.
  • Debezium rasmiy hujjatlari — Change Data Capture (CDC), Outbox Event Router (outbox pattern muqobili).
  • Vaughn Vernon — Implementing Domain-Driven Design — domain event, integration event, bounded context orasidagi event integratsiyasi.
  • Greg Young — Event Sourcing va CQRS ma'ruzalari (snapshot, event versioning/upcasting tamoyillari).

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
9.7-bob: Event-driven architecture, CQRS, Event Sourcing — Wisar