Bu darsda nima bor
1–4-qismlar Event Sourcing’ni noldan quradi: nega kerak, eventlarni saqlash g‘oyasi, event store va rehydration, NestJS AggregateRoot implementatsiya. 5–6-qismlar — snapshot (tezlik), CQRS bilan bog‘lanish va afzallik/qiyinchiliklar. 7-qism qachon ES kerak (va qachon ortiqcha) ekanini hal qiladi.
Nega kerak — tarix yo‘qoladi
Oddiy o‘xshatish: bank hisobida shunchaki "balans = 100" deb yozilsa, bu pul qayerdan kelganini bilmaysiz. Lekin bank har bir tranzaksiyani (kirim, chiqim) yozadi — balans esa ulardan kelib chiqadi. Daftar (ledger) — haqiqat, balans — natija.
An’anaviy CRUD’da biz faqat joriy holatni saqlaymiz: UPDATE orders SET status='shipped'. Eski holat ustiga yoziladi va yo‘qoladi.
1. "Bu buyurtma nega bekor qilindi? Kim, qachon o‘zgartirdi?" — javob yo‘q, faqat oxirgi holat bor.
2. Audit (kim nima qildi) yo‘q — moliya, tibbiyot, huquqda bu qabul qilinmaydi.
3. "O‘tgan seshanba holat qanday edi?" (temporal so‘rov) — javob bera olmaysiz.
4. Bug topilsa, "qanday shu holatga keldik?"ni qayta tiklab bo‘lmaydi.
Joriy holat o‘rniga, unga olib kelgan barcha eventlar ketma-ketligini saqlash. Joriy holat — eventlarni qayta o‘ynatish (replay) orqali hosil qilinadi. Eventlar — o‘zgarmas (immutable), faqat qo‘shiladigan (append-only) faktlar. Event jurnali — haqiqat manbai.
Asosiy g‘oya — eventlarni saqlash
State emas, faktlar oqimini saqlaymiz. Joriy holat — ularning yig‘indisi.
Tanish analogiya: Git ham shunday ishlaydi — u faqat fayllarning oxirgi holatini emas, har bir commit (o‘zgarish)ni saqlaydi; istalgan vaqtga qaytish, tarixni ko‘rish mumkin. Event Sourcing — ma’lumotlaringiz uchun "git".
Append-only: eventlar hech qachon o‘zgartirilmaydi yoki o‘chirilmaydi, faqat yangisi qo‘shiladi. Immutable: bo‘lib o‘tgan fakt — o‘zgarmaydi. O‘tgan zamon: eventlar nomlari fakt sifatida (OrderCreated, ItemAdded, OrderShipped) — niyat (command) emas, bo‘lib o‘tgan narsa.
Event store va rehydration
stream · fold · rehydrationEventlar har aggregat (masalan, har buyurtma) uchun alohida oqimda (stream) saqlanadi. Joriy holatni olish uchun ularni yuklab, tartib bilan qo‘llaymiz — bu rehydration (qayta tiklash).
Misol: bo‘sh buyurtma → OrderCreated (status=draft) → ItemAdded (2 ta mahsulot) → ItemAdded (yana 1 ta) → OrderShipped (status=shipped). Eventlarni boshidan qo‘llasak — aniq joriy holatga kelamiz. Bu deterministik: bir xil eventlar → har doim bir xil holat.
Farqni eslang (3 va 9-mavzular): Command — "buyurtma yarat" (niyat, rad etilishi mumkin). Event — "buyurtma yaratildi" (bo‘lib o‘tgan fakt, o‘zgarmaydi). Command muvaffaqiyatli bajarilsa — bir yoki bir nechta event chiqaradi va aynan o‘shalar saqlanadi.
NestJS implementatsiya (AggregateRoot)
apply · loadFromHistoryNestJS’ning @nestjs/cqrs moduli Event Sourcing uchun AggregateRoot beradi: u apply() bilan event chiqaradi va loadFromHistory() bilan eventlardan tiklanadi.
1 · Eventlar
// Eventlar — O'TGAN ZAMONDA bo'lib o'tgan faktlar (immutable)
export class OrderCreatedEvent { constructor(public id: string, public userId: string) {} }
export class ItemAddedEvent { constructor(public id: string, public sku: string, public qty: number) {} }
export class OrderShippedEvent { constructor(public id: string) {} }
2 · Aggregate — eventlardan holat quradi
// Aggregate — eventlardan o'z holatini quradi (@nestjs/cqrs)
export class Order extends AggregateRoot {
private id: string;
private items: { sku: string; qty: number }[] = [];
private status: "draft" | "shipped" = "draft";
// COMMAND metodlari: tekshiradi va YANGI event chiqaradi (apply)
createOrder(id: string, userId: string) {
this.apply(new OrderCreatedEvent(id, userId)); // fakt qo'shildi
}
addItem(sku: string, qty: number) {
if (this.status === "shipped") throw new Error("Yuborilgan buyurtma yopilgan");
this.apply(new ItemAddedEvent(this.id, sku, qty));
}
// EVENT HANDLER'lar: event holatga QANDAY ta'sir qilishini belgilaydi
onOrderCreatedEvent(e: OrderCreatedEvent) { this.id = e.id; this.status = "draft"; }
onItemAddedEvent(e: ItemAddedEvent) { this.items.push({ sku: e.sku, qty: e.qty }); }
onOrderShippedEvent(e: OrderShippedEvent) { this.status = "shipped"; }
}
3 · Event store — append va rehydration
// Holatni QAYTA QURISH (rehydration) va saqlash (append-only)
async load(orderId: string): Promise<Order> {
const events = await this.eventStore.getEvents(orderId); // shu aggregat eventlari
const order = new Order();
order.loadFromHistory(events); // har eventni navbat bilan apply -> joriy holat
return order;
}
async save(order: Order) {
const newEvents = order.getUncommittedEvents(); // faqat YANGI eventlar
await this.eventStore.append(order.id, newEvents); // APPEND (insert), update emas
order.commit(); // eventlarni publish (projection'larga)
}
Event store — eventlarni saqlaydigan append-only ombor. Maxsus yechim (EventStoreDB) yoki oddiy PostgreSQL jadval (events(aggregate_id, version, type, data, created_at)) bo‘lishi mumkin. Sizning stack’ingizda Postgres jadvali bilan ham boshlash mumkin.
Snapshot va CQRS bilan bog‘lanish
snapshot · projectionEvent Sourcing ikki tabiiy hamrohga ega: tezlik uchun snapshot, o‘qish uchun CQRS.
Snapshot — minglab eventni qayta o‘ynatmaslik
Bir aggregatda 10000 ta event bo‘lsa, har yuklashda hammasini qayta o‘ynatish sekin.
Yechim — snapshot: davriy ravishda joriy holatni saqlab qo‘yish; yuklashda snapshot’dan boshlab, faqat undan keyingi eventlarni qo‘llash:
// SNAPSHOT: minglab eventni har safar qayta o'ynatish sekin -> davriy holat saqlaymiz
async load(orderId: string): Promise<Order> {
const snap = await this.snapshots.getLatest(orderId); // oxirgi snapshot (masalan, v100)
const order = snap ? Order.fromSnapshot(snap) : new Order();
const from = snap ? snap.version : 0;
const events = await this.eventStore.getEvents(orderId, { after: from }); // FAQAT keyingilari
order.loadFromHistory(events); // v100 dan keyingi eventlar
return order;
}
Nega CQRS bilan juft (9-mavzu)
Event store "buyurtma #5 holati"ni tez beradi, lekin "barcha yuborilgan buyurtmalar" kabi so‘rovni bera olmaydi — buning uchun har birini qayta o‘ynatish kerak bo‘lardi. Yechim: eventlardan projection (CQRS read model) qurish. Shuning uchun ES deyarli doim CQRS bilan keladi:
Afzalliklar va qiyinchiliklar
audit · event versioningEvent Sourcing kuchli imkoniyatlar beradi, lekin jiddiy qiyinchiliklar ham keltiradi. Senior ikkalasini ham biladi.
Afzalliklar
- To‘liq audit/tarix — har o‘zgarish abadiy saqlanadi (moliya, huquq)
- Temporal so‘rov — istalgan o‘tmish holatini tiklash mumkin
- Debug/replay — bug holatini qayta o‘ynatib tahlil qilish
- Istalgan read modelni qurish — eventlardan yangi ko‘rinish (9-mavzu rebuild)
- Tabiiy event-driven — eventlar allaqachon bor (3-mavzu)
Qiyinchiliklar
- Event versiyalash — eventlar abadiy yashaydi; sxema vaqt o‘tib o‘zgarsa, eskilarini ham qo‘llay olish kerak
- O‘chirish qiyin — append-only; GDPR "o‘chirish huquqi" murakkab
- O‘qish to‘g‘ridan emas — joriy holat uchun projection kerak (eventual consistency)
- O‘rganish egriligi — jamoaga yangi fikrlash tarzi
Bugun saqlagan event 5 yildan keyin ham o‘qilishi kerak. Lekin biznes o‘zgaradi, event tuzilishi o‘zgaradi. Eski eventlarni yangi shaklga moslashtirish (upcasting) — boshidan rejalanishi shart. Bu Deep qatlamning asosiy mavzusi.
Qachon Event Sourcing (va qachon yo‘q)
Muhim: Event Sourcing — eng og‘ir naqshlardan biri. Oddiy CRUD ilovaga uni qo‘llash — katta xato (over-engineering). To‘g‘ri joyda esa — beqiyos qiymat.
ES qachon arziydi
- Audit/tarix — asosiy talab (moliya, bank, tibbiyot, huquq)
- Temporal so‘rovlar kerak ("o‘sha paytda qanday edi")
- Boy domen, murakkab xulq-atvor
- "Nega shunday bo‘ldi" — debug/tahlil muhim
ES qachon ortiqcha
- Oddiy CRUD (CRUD yetadi)
- Tarix muhim emas
- Jamoa tajribasi/tayyorligi yo‘q
- Kichik, oddiy domen
1. Butun tizimga emas, faqat tarix/audit muhim bo‘lgan aggregatlarga qo‘lla (masalan, faqat "to‘lov" yoki "buyurtma"). Qolgan qismi oddiy CRUD bo‘laversin.
2. O‘qish uchun CQRS projectionni boshidan rejala (ES yolg‘iz so‘rovga noqulay).
3. Event versiyalash intizomini birinchi kundan o‘rnat (eventlar abadiy).
4. Snapshot strategiyasini katta oqimlar uchun rejala.
Eslab qolish kerak bo‘lgan 5 jumla
- Event Sourcing = joriy holat o‘rniga barcha eventlarni (o‘zgarmas, append-only faktlar) saqlash. Holat — eventlarni qayta o‘ynatish (rehydration) natijasi.
- Event = o‘tgan zamondagi fakt (
OrderShipped); command = niyat. Event jurnali — haqiqat manbai. Analogiya: bank daftari, git. - NestJS:
AggregateRoot+apply()(event chiqarish) +loadFromHistory()(tiklash); event store append-only. - Ikki hamroh: snapshot (minglab eventni qayta o‘ynamaslik) va CQRS (projection — o‘qish uchun, chunki ES so‘rovga noqulay).
- Afzallik: audit, tarix, temporal, replay. Qiyinchilik: event versiyalash, o‘chirish (GDPR), murakkablik. Faqat tarix muhim aggregatlarga qo‘lla — CRUD uchun ishlatma.
Bu mavzuning Deep/Senior qatlami (event versiyalash/upcasting, snapshot strategiyalari, optimistik concurrency + expectedVersion, GDPR/crypto-shredding, event store tanlovi — EventStoreDB vs Postgres vs Kafka, process manager/saga eventlardan, katta oqimlar) kerak bo‘lsa — ayting.
Yoki keyingi mavzu: “11-mavzu: CAP Theorem” — taqsimlangan tizimlarning asosiy qonuni.