Bu qatlamda nima bor
Core darslik asoslarni berdi. Bu Deep qatlam senior darajadagi chuqur mexanikani ochadi: event versiyalash va upcasting, optimistik concurrency (expectedVersion), snapshot strategiyalari, event store tanlovi (EventStoreDB/Postgres/Kafka), GDPR uchun crypto-shredding, process manager/saga va katta oqimlar bilan ishlash. Oxirida — yig‘ma arxitektura va senior cheklist.
Event versiyalash va upcasting
upcasting · tolerant readerEng katta senior muammosi: eventlar abadiy yashaydi. Bugun saqlagan event 5 yildan keyin ham o‘qiladi — lekin biznes va event tuzilishi o‘zgaradi. Eski eventlarni hech qachon o‘zgartirib bo‘lmaydi (immutable).
Uchta strategiya
- Weak/tolerant schema: yangi maydonlarni ixtiyoriy (default bilan) qo‘shish; o‘quvchi yo‘q maydonni e’tiborsiz qoldiradi. Eng oddiy, lekin faqat qo‘shish uchun.
- Versiyalangan eventlar:
OrderCreated.v1,OrderCreated.v2— yangi shakl yangi versiya sifatida. - Upcasting: o‘qishda eski eventni yangi shaklga aylantirish (saqlangan event tegilmaydi).
// Eski event (v1) va yangi (v2): manzil oddiy string edi -> obyektga aylandi
type OrderCreatedV1 = { id: string; address: string };
type OrderCreatedV2 = { id: string; address: { city: string; street: string } };
// UPCASTER: O'QISHDA eski eventni yangi shaklga aylantiradi
// (saqlangan event O'ZGARMAYDI — faqat o'qiyotganda transformatsiya)
function upcast(e: StoredEvent): DomainEvent {
if (e.type === "OrderCreated.v1") {
return {
type: "OrderCreated.v2",
data: { id: e.data.id, address: parseAddress(e.data.address) }, // string -> obyekt
};
}
return e; // allaqachon yangi versiya
}
Maydon qo‘shish oson. Lekin event bo‘lish/birlashtirish (bitta eski event → ikkita yangi, yoki aksincha), nom o‘zgartirish — upcaster pipeline orqali bosqichma-bosqich (v1→v2→v3) qilinadi. Qoida: upcasting faqat oldinga, saqlangan eventga hech qachon teginma.
Optimistik concurrency + expectedVersion
expectedVersion · primary keyEvent-sourced aggregat uchun concurrency nazorati — expectedVersion. Bu CQRS Deep’dagi optimistik concurrency’ning event store varianti.
Ikki command bir aggregatni (version 5) yuklab, ikkalasi ham event qo‘shmoqchi. Agar tekshiruvsiz yozsa — ikkalasi version 6 yozadi, biri ikkinchisini ustiga yozadi yoki tarix buziladi.
Yechim — expectedVersion bilan append
Aggregat yuklanganda uning joriy versiyasini bilamiz. Append qilganda "men 5-versiyani ko‘rgandim, 6 ga yozmoqchiman" deymiz. Agar oraliqda kimdir 6 ni yozib qo‘ygan bo‘lsa — yozuv rad etiladi (reload + retry). Postgres’da buni PRIMARY KEY (aggregate_id, version) tabiiy beradi:
// Postgres event store — optimistik concurrency PRIMARY KEY orqali
// CREATE TABLE events (aggregate_id UUID, version INT, type TEXT, data JSONB,
// PRIMARY KEY (aggregate_id, version)); <- bir version ikki marta yozilmaydi
async append(aggregateId: string, expectedVersion: number, events: DomainEvent[]) {
try {
await this.prisma.$transaction(
events.map((e, i) =>
this.prisma.event.create({
data: {
aggregateId,
version: expectedVersion + i + 1, // 6, 7, ... (kutilgan + 1 dan)
type: e.type,
data: e as any,
},
}),
),
);
} catch (err) {
if (isUniqueViolation(err)) // (aggregate_id, version) band -> kimdir yozdi
throw new ConcurrencyException("Concurrent modification — reload & retry");
throw err;
}
}
Snapshot strategiyalari
snapshot policy · versioningCore’da snapshot’ni ko‘rdik. Senior darajada muhimi — qachon olish, qayerda saqlash va snapshot ham versiyalanishini tushunish.
Strategiyalar
- Qachon: har N eventda (masalan, 100), yoki vaqt bo‘yicha, yoki yuklashda "juda ko‘p event qoldi" deb aniqlanganda.
- Qayerda: alohida snapshot ombori (event store’dan ajratilgan) — eventlarni bulg‘amaydi.
- Versiyalash: snapshot — bu aggregat holatining seriyalashtirilgan shakli; holat sxemasi o‘zgarsa, eski snapshot o‘qib bo‘lmaydi.
// SNAPSHOT siyosati — bu OPTIMIZATSIYA, hech qachon haqiqat manbai emas
async save(order: Order) {
await this.appendEvents(order);
if (order.version % 100 === 0) { // siyosat: har 100 eventda
await this.snapshots.save(order.id, {
version: order.version,
state: order.toSnapshot(),
schemaVersion: 3, // snapshot SXEMASI ham versiyalanadi
});
}
}
async load(id: string): Promise<Order> {
const snap = await this.snapshots.getLatest(id);
// snapshot sxemasi eskirgan/mos kelmasa -> tashlab, eventlardan TO'LIQ qur
const usable = snap && snap.schemaVersion === 3;
const order = usable ? Order.fromSnapshot(snap) : new Order();
const from = usable ? snap.version : 0;
order.loadFromHistory(await this.eventStore.getEvents(id, { after: from }));
return order;
}
Snapshot — kesh/optimizatsiya, hech qachon haqiqat manbai emas. Uni har doim eventlardan qayta qurish mumkin bo‘lishi shart. Snapshot sxemasi mos kelmasa — uni shunchaki tashlab, eventlardan to‘liq tiklaysan. Snapshot’ni yo‘qotsang — hech narsa yo‘qolmaydi (faqat sekinroq yuklanadi).
Event store tanlovi
EventStoreDB · Postgres · KafkaEvent store tanlovi muhim qaror. Uch asosiy variant bor — har biri boshqa kelishuv bilan.
| EventStoreDB | PostgreSQL jadval | Kafka | |
|---|---|---|---|
| Tabiati | Maxsus event store | Umumiy RDBMS | Taqsimlangan log |
| Aggregat oqimi yuklash | O‘rnatilgan (streams) | Oddiy WHERE aggregate_id | Qiyin (log, kalit bo‘yicha tasodifiy o‘qish yo‘q) |
| Optimistik concurrency | O‘rnatilgan (ExpectedVersion) | PRIMARY KEY bilan o‘zing | Yo‘q (tabiiy emas) |
| Subscription/projection | O‘rnatilgan (catch-up) | LISTEN/NOTIFY yoki polling | A’lo (consumer groups) |
| Eng yaxshi rol | To‘liq event store | Oddiy boshlash, tranzaksion | Transport (haqiqat manbai emas) |
Postgres jadval — sizning stack’ingizda eng amaliy boshlang‘ich: tranzaksion, PRIMARY KEY (aggregate_id, version) bilan concurrency, JSONB bilan moslashuvchan. EventStoreDB — ES asosiy bo‘lsa va ko‘p stream/subscription kerak bo‘lsa. Kafka — ajoyib event transport, lekin haqiqat manbai sifatida emas: u log, bitta aggregat oqimini versiya bo‘yicha yuklash va optimistik concurrency uchun mo‘ljallanmagan. Ko‘pincha: Postgres (haqiqat) + Kafka (tarqatish).
Crypto-shredding va unutilish
crypto-shredding · PIIEvent Sourcing’ning huquqiy qiyinchiligi: append-only va GDPR’ning "unutilish huquqi" (o‘chirishni talab qiladi) bir-biriga zid. Eventni o‘chirib bo‘lmaydi — qanday "unutamiz"?
Foydalanuvchi "mening barcha ma’lumotimni o‘chiring" deydi. Lekin eventlar immutable, append-only — o‘chirsang, tarix buziladi, snapshotlar va projectionlar mos kelmaydi.
Yechim — crypto-shredding
PII (shaxsiy ma’lumot)ni eventda shifrlangan holda saqla, kalitni esa alohida (har foydalanuvchi uchun). "Unutish" — eventni emas, kalitni o‘chirish: shifrlangan ma’lumot endi tiklanmaydi (amalda o‘chirilgan), lekin event jurnalining tuzilishi buzilmaydi:
// CRYPTO-SHREDDING (GDPR): append-only event'ni o'chirib bo'lmaydi.
// Yechim: PII'ni har foydalanuvchi uchun alohida kalit bilan SHIFRLAYMIZ.
function storeEvent(e: any, userKey: Buffer) {
return { ...e, email: encrypt(e.email, userKey) }; // PII shifrlangan saqlanadi
}
// "Unutilish huquqi" (right to be forgotten):
await keyStore.delete(userId); // foydalanuvchi kalitini O'CHIRAMIZ
// -> eventlar joyida qoladi (append-only buzilmaydi),
// lekin shifrlangan PII endi HECH QACHON o'qilmaydi = amalda o'chirilgan
PII’ni ajratish: shaxsiy ma’lumotni umuman eventga solmaslik (alohida, o‘chiriladigan jadvalda saqlash, eventda faqat ID). Crypto-shredding mukammal emas (kalit boshqarish murakkab, eski backuplar masalasi) — lekin ES’da GDPR uchun amaldagi standart yondashuv. Buni boshidan rejalashtirish kerak.
Process Manager / Saga eventlardan
orchestration · compensationUzoq davom etadigan biznes jarayonlarini (buyurtma → rezerv → to‘lov → yuborish) eventlar yordamida muvofiqlashtiramiz — bu Process Manager (yoki Saga).
Process Manager nima
U eventlarni tinglaydi, o‘z holatini yuritadi (jarayon qaysi bosqichda) va navbatdagi commandni chiqaradi. Ya’ni: event kiradi → holat yangilanadi → command chiqadi.
// PROCESS MANAGER: eventlarga reaksiya qilib, O'Z holatini yuritadi va COMMAND chiqaradi
@EventsHandler(OrderPlacedEvent)
export class OrderProcessManager {
constructor(private commands: CommandBus) {}
async handle(e: OrderPlacedEvent) {
// 1-qadam: inventarni rezerv qil
await this.commands.execute(new ReserveInventoryCommand(e.orderId, e.items));
}
}
@EventsHandler(InventoryReservedEvent)
export class OnInventoryReserved {
async handle(e: InventoryReservedEvent) {
// 2-qadam: to'lovni amalga oshir
await this.commands.execute(new ChargePaymentCommand(e.orderId));
}
}
// muvaffaqiyatsizlikda -> KOMPENSATSIYA (masalan, ReleaseInventory) -> 13-mavzu Saga
1-mavzudagi Saga’ni eslang. Choreography — markaziy boshqaruvsiz, har xizmat eventga o‘zi reaksiya qiladi (oddiy, lekin tarqoq mantiq). Orchestration / Process Manager — markaziy, holatli koordinator (murakkab jarayon uchun aniqroq). Muvaffaqiyatsizlikda kompensatsiya (orqaga qaytaruvchi amal) — 13-mavzu (Distributed Transactions) mavzusi.
Katta oqimlar va performance
aggregate design · catch-upBitta aggregatda millionlab event to‘planib qolsa — bu odatda dizayn xatosi belgisi. Senior aggregat chegaralarini to‘g‘ri tortishni biladi.
Masalan, "do‘kon" aggregati har sotuvni o‘ziga event qilsa — yillar o‘tib millionlab event, yuklash imkonsiz sekinlashadi, snapshot ham yetmaydi.
1. Aggregatni kichik tut: "do‘kon" emas, har "buyurtma" alohida aggregat (qisqa umrli, kam eventli). Bu — eng muhim yechim (DDD aggregat dizayni).
2. Oqimni yopish/aylantirish: davriy oqimlar (masalan, har oy/yil yangi oqim), eskisi arxivlanadi.
3. Snapshot (3-bo‘lim) uzunroq oqimlar uchun.
Masshtabda projection — catch-up subscription
Ko‘p eventli tizimda projectionlar eventlarni catch-up subscription orqali ketma-ket o‘qiydi: checkpoint’dan boshlab, yangilarini olib, read modelni yangilaydi (9-mavzu Deep: idempotency + checkpoint). Bu projectionlarni event oqimi bilan moslashtiradi va rebuild’ni mumkin qiladi.
Har aggregat o‘z oqimiga ega (order-123). Bundan tashqari "kategoriya oqimi" (order-*) — barcha buyurtma eventlari ketma-ket — projectionlar uchun qulay (bitta joydan hammasini o‘qish).
Yig‘ma ko‘rinish va senior cheklist
To‘liq Event Sourcing arxitekturasi — yig‘ma ko‘rinish
| Bosqich | Nima bo‘ladi | Bo‘lim |
|---|---|---|
| Aggregatni yuklash | Snapshot + undan keyingi eventlar (upcast bilan) | 1, 3 |
| Command bajarish | Validatsiya + yangi event(lar) chiqarish | — |
| Append | expectedVersion bilan optimistik concurrency | 2 |
| Publish | Eventlar projection va process manager‘larga | 6 |
| Projection (CQRS) | Idempotent + checkpoint read model | 7 |
| GDPR | PII crypto-shredding bilan | 5 |
Senior tayyorlik cheklisti
| Savol / amaliyot | Bo‘lim |
|---|---|
| Event versiyalash va upcasting’ni boshidan rejalayman (eventlar abadiy) | 1 |
| expectedVersion bilan optimistik concurrency qo‘llayman | 2 |
| Snapshot — optimizatsiya ekanini, versiyalanishini, qayta qurilishini bilaman | 3 |
| Event store tanlayman (Postgres boshlash; Kafka = transport, store emas) | 4 |
| GDPR’ni crypto-shredding yoki PII ajratish bilan hal qilaman | 5 |
| Uzoq jarayonlarni process manager bilan boshqaraman; kompensatsiyani bilaman | 6 |
| Aggregatni kichik tutaman; katta oqim/catch-up subscription’ni boshqaraman | 7 |
Bu 10-mavzuning Deep/Senior qatlami edi — Core bilan birga, Event Sourcing endi to‘liq.
Endi yozish/o‘qish naqshlari (9–10) yakunlandi. Keyingi mavzu: “11-mavzu: CAP Theorem” — taqsimlangan tizimlarning asosiy qonuni (izchillik vs mavjudlik). Core’dan boshlaymiz.