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)
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
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
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 murakkabEDA 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)
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)
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)
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)
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)
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)
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) — mustaqilUchalasi 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)
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
// 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)
"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)
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)
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/cqrsSaga(rxjs oqimi bilan event yangi command) — choreography'ga yaqin. Murakkab jarayonda orchestration afzal.
2.15. Event schema evolution va upcasting (Event Sourcing)
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 (
schemaVersionhar event ichida); (2) upcasting — o'qish/replay paytida eski event yangi shaklga aylantiriladi (v1 v2 v3upcaster 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)
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 (
PaymentReceivedikki marta ikki marta yechish). Yechim: (1) event ID +processed_eventsjadvali — event kelganda id oldin ishlanganmi tekshiriladi (ha tashlanadi; yo'q ishlanadi va id shu tranzaksiyada yoziladi — atomik); (2) tabiiy idempotentlik —UPSERT/ 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)
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)
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
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 | @EventsHandler4. Batafsil kod namunalari
Misol 1 — Event-driven (EDA — monolit — 2.1)
// 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)
// 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)
// === 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)
// 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)
// 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)
// 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)
// 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)
// 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)
// 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)
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'lgandaMisol 11 — Idempotent consumer (event takror kelganda — 2.16)
// 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)
// 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)
// 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
oddiy ma'lumotga event sourcing (over-engineering — 2.10)
oddiy service (CRUD)2) Event'ni o'zgartirish
saqlangan event'ni tahrirlash (fakt emas — 2.2)
immutable (yangi event qo'shish)3) Dual write (DB + event alohida)
DB saqla + event yubor (atomik emas — yo'qolishi — 2.8)
Outbox pattern (bir tranzaksiya)4) Eventual'ni e'tiborsiz
yozgach darrov read'dan o'qish (stale — 2.7)
write natijasi / optimistik UI / poll5) Strong consistency'ga eventual
bank balansiga eventual (xato — 2.7)
strong consistency (ACID — monolit)6) Event takrorini e'tiborsiz
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
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
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)
- EDA: event + tinglovchilar (Misol 1, 2.1).
- Event class: immutable (Misol 2, 2.2).
- CQRS: command/query (Misol 3, 2.11).
- Projection: event read model (Misol 4, 2.5).
- Event Sourcing: event store + replay (Misol 5, 2.6).
- Snapshot: tezlik (Misol 6).
- Outbox: ishonchli event (Misol 7, 2.8).
- Eventual consistency: boshqaruv (Misol 8, 2.7).
- Mikroservis EDA: Kafka (Misol 9, 9.6).
- Idempotent consumer: takror event (Misol 11, 2.16).
- Saga: ikki hisob orasida o'tkazma + kompensatsiya (Misol 13, 2.14).
- 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!