Bu darsda nima bor
1–4-qismlar CQRS’ni noldan quradi: nega kerak, ikki tomonga ajratish, Command vs Query farqi va NestJS @nestjs/cqrs implementatsiya. 5–6-qismlar — kuchli ko‘rinish: alohida o‘qish modeli (projection) va uning narxi (eventual consistency). 7-qism qachon CQRS kerak (va qachon ortiqcha) ekanini hal qiladi.
Nega kerak — bitta model siqilishi
Oddiy o‘xshatish: oshxonada yozish (ovqat tayyorlash — tartib, retsept, sifat muhim) va berish (mijozga chiroyli laganda taqdim etish) — ikki xil ish. Bittasiga moslangan jarayon ikkinchisiga noqulay.
Odatda bitta model (masalan, Order entity) ham yozuv, ham o‘qish uchun ishlatiladi.
O‘qish va yozishning ehtiyojlari tubdan farq qiladi:
Yozuv — validatsiya, biznes qoidalari, izchillik, normalizatsiya (toza, takrorlanmagan ma’lumot) talab qiladi.
O‘qish — tez, denormalizatsiya qilingan, UI’ga moslangan shaklni xohlaydi.
Bittada ikkalasiga xizmat qilmoqchi bo‘lsang — murosalar boshlanadi: dashboard uchun 5 ta jadvalni JOIN qilasan, model shishadi, so‘rovlar sekinlashadi.
CQRS (Command Query Responsibility Segregation) — o‘qish va yozish mas’uliyatini ajratish. Ikki alohida model: Command (yozish — holatni o‘zgartiradi) va Query (o‘qish — ma’lumot qaytaradi). Har biri o‘z ehtiyojiga to‘liq moslanadi.
CQRS — Bertrand Meyer’ning CQS (Command-Query Separation) prinsipining arxitektura darajasiga ko‘tarilgani: metod yo biror ish qiladi (command, hech narsa qaytarmaydi), yo savolga javob beradi (query, holatni o‘zgartirmaydi) — ikkalasini birga emas.
Asosiy g‘oya — ikki tomonga ajratish
CQRS tizimni ikki tomonga ajratadi — har biri mustaqil optimallashtiriladi.
Eng muhim tushuncha: CQRS — bu spektr, "hammasi yoki hech narsa" emas. Eng oddiy darajada — shunchaki kodni command va query qismlariga ajratasan (bitta baza). Eng kuchli darajada — alohida o‘qish ombori (denormalizatsiya, event bilan sinxron). Kichikdan boshlash mumkin.
Command va Query — aniq farq
Ikki tomonni aniq farqlash kerak — ular butunlay boshqacha xulq-atvorga ega:
| Command (yozuv) | Query (o‘qish) | |
|---|---|---|
| Maqsad | Holatni o‘zgartirish | Ma’lumot qaytarish |
| Nomi | Buyruq: CreateOrder, CancelOrder | Savol: GetUserOrders |
| Qaytaradi | Hech narsa / tasdiq (ack) | UI’ga moslangan DTO |
| Yo‘li | Validatsiya + biznes logika orqali | Domen logikasini chetlab, to‘g‘ridan-to‘g‘ri |
| Side-effect | Bor (yozadi) | Yo‘q (faqat o‘qiydi) |
Command niyatni ifodalaydi ("buyurtma yarat") va biznes qoidalaridan o‘tadi. Query esa shunchaki o‘qishga optimallashtirilgan ma’lumotni qaytaradi — biznes logika, validatsiya yo‘q, faqat tez javob. Bu ajratish har ikki tomonni soddalashtiradi.
NestJS implementatsiya (@nestjs/cqrs)
CommandBus · QueryBusNestJS’da CQRS uchun rasmiy modul bor: @nestjs/cqrs — CommandBus, QueryBus va handler’lar.
1 · Command va uning handleri
// COMMAND — niyat (imperative). @nestjs/cqrs
export class CreateOrderCommand {
constructor(
public readonly userId: string,
public readonly items: OrderItem[],
) {}
}
@CommandHandler(CreateOrderCommand)
export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
constructor(private prisma: PrismaService) {}
async execute(cmd: CreateOrderCommand) {
// validatsiya + biznes logika + YOZISH (write model)
return this.prisma.order.create({
data: { userId: cmd.userId, items: { create: cmd.items } },
});
}
}
2 · Query va uning handleri
// QUERY — ma'lumot so'raydi (side-effect yo'q), UI uchun moslangan DTO qaytaradi
export class GetUserOrdersQuery {
constructor(public readonly userId: string) {}
}
@QueryHandler(GetUserOrdersQuery)
export class GetUserOrdersHandler implements IQueryHandler<GetUserOrdersQuery> {
constructor(private prisma: PrismaService) {}
async execute(q: GetUserOrdersQuery) {
// o'qishga moslangan natija (denormalizatsiya qilingan view)
return this.prisma.orderView.findMany({ where: { userId: q.userId } });
}
}
3 · Controller — bus’lar orqali
// Controller — CommandBus va QueryBus orqali yo'naltiradi
@Controller('orders')
export class OrdersController {
constructor(private commands: CommandBus, private queries: QueryBus) {}
@Post() // yozuv -> command
create(@Body() dto: CreateOrderDto, @CurrentUser() userId: string) {
return this.commands.execute(new CreateOrderCommand(userId, dto.items));
}
@Get() // o'qish -> query
list(@CurrentUser() userId: string) {
return this.queries.execute(new GetUserOrdersQuery(userId));
}
}
Endi har bir amal — alohida, nomlangan, test qilinadigan birlik (handler). Yozuv yo‘li (validatsiya, domen) va o‘qish yo‘li (tez DTO) bir-biriga xalaqit bermaydi. Controller esa shunchaki bus’ga uzatadi — yupqa qoladi.
Alohida o‘qish modeli (projection)
read model · projectionCQRS’ning eng kuchli ko‘rinishi — alohida o‘qish modeli (read model / projection): o‘qish uchun maxsus, denormalizatsiya qilingan ombor. Bu yerda CQRS event-driven (3-mavzu) bilan birlashadi.
Read model qanday quriladi
Yozuv normalizatsiya qilingan asosiy bazaga boradi. Har yozuvda event chiqadi, va projection uni o‘qib, o‘qishga moslangan omborni (denormalizatsiya — JOIN’siz, tayyor) yangilaydi:
// PROJECTION: yozuv tomonidan event chiqadi -> o'qish modelini yangilaymiz
@EventsHandler(OrderCreatedEvent)
export class OrderProjection implements IEventHandler<OrderCreatedEvent> {
constructor(private prisma: PrismaService) {}
async handle(event: OrderCreatedEvent) {
// o'qishga optimallashtirilgan jadval: JOIN'siz, hammasi TAYYOR
await this.prisma.orderView.create({
data: {
orderId: event.orderId,
userId: event.userId,
userName: event.userName, // DENORMALIZATSIYA: user nomi shu yerda saqlanadi
total: event.total,
status: 'created',
},
});
}
}
O‘qish ombori turli bo‘lishi mumkin: o‘sha bazada alohida jadval (materialized view), yoki Redis (juda tez), yoki Elasticsearch (murakkab qidiruv). Hatto bir nechta o‘qish modeli — har UI ekrani uchun alohida (dashboard view, search view).
Eventual consistency va kelishuvlar
eventual consistencyAlohida o‘qish ombori kuchli, lekin u narx bilan keladi: read model yozuv modelidan biroz orqada qoladi — pirovard izchillik (eventual consistency).
Projection async ishlaganda, yozuv bo‘ldi-yu, read model hali yangilanmagan bo‘lishi mumkin (millisekundlar). Foydalanuvchi buyurtma yaratdi, ro‘yxatni ochdi — lekin hali ko‘rmasligi mumkin. Bu — 7-mavzudagi read-your-writes muammosining aynan o‘zi.
1. UI’da optimistik ko‘rsatish (foydalanuvchining o‘z amalini darhol ekranda ko‘rsat, fonda tasdiqlan). 2. Muhim joyda yozuv modelidan o‘qi (yangi yaratilgan elementni write’dan qaytar). 3. Projection’ni juda tez (yoki sinxron) qil. 4. Foydalanuvchiga "yangilanmoqda" holatini ko‘rsat.
CQRS alohida baza talab qilmaydi. Read va write bir bazada (faqat alohida kod yoki alohida jadval) bo‘lsa — pirovard izchillik muammosi umuman bo‘lmaydi (sinxron o‘qiysan). Eventual consistency faqat omborlarni ajratganda paydo bo‘ladi. Shuning uchun kichikdan boshla.
Qachon CQRS (va qachon yo‘q)
Muhim: CQRS — kuchli, lekin hamma narsa uchun emas. Oddiy CRUD ilovaga uni qo‘llash — keraksiz murakkablik (over-engineering). To‘g‘ri joyda esa — ajoyib.
CQRS darajalari — kichikdan boshla
- 1-daraja (arzon): faqat kodni command/query handler’larga ajratish (bitta baza). Toza, testlanadigan — ko‘pchilik uchun shu yetadi.
- 2-daraja (o‘rta): o‘sha bazada alohida o‘qish modeli (denormalizatsiya qilingan view jadval).
- 3-daraja (qimmat): alohida o‘qish ombori (Redis/ES) + event bilan sinxron → pirovard izchillik.
CQRS qachon foydali
- O‘qish va yozish yuki/shakli tubdan farq qilganda
- Murakkab biznes domen (boy yozuv logikasi)
- O‘qish uchun boshqa ombor kerak (qidiruv, masshtab)
- Event Sourcing (10-mavzu) bilan birga
CQRS qachon ortiqcha
- Oddiy CRUD ilova
- O‘qish va yozish bir xil shaklda
- Kichik domen, kam logika
- Jamoa eventual consistency’ga tayyor emas
1. Kichikdan boshla — avval 1-daraja (kod ajratish). Ehtiyoj paydo bo‘lgandagina keyingi darajaga o‘t.
2. Butun tizimga emas, faqat kerakli bounded contextga qo‘lla (masalan, faqat "buyurtma" moduliga).
3. Alohida ombor qo‘shsang — eventual consistencyni boshidan rejala (UI va read-your-writes).
Eslab qolish kerak bo‘lgan 5 jumla
- CQRS = o‘qish va yozish mas’uliyatini ajratish: Command (yozuv — o‘zgartiradi) va Query (o‘qish — qaytaradi). CQS prinsipining arxitektura darajasi.
- Sabab: o‘qish va yozish ehtiyojlari tubdan farq qiladi — yozuv izchillik/normalizatsiya, o‘qish tezlik/denormalizatsiya xohlaydi.
- NestJS:
@nestjs/cqrs—CommandBus/QueryBus+ handler’lar; controller yupqa qoladi. - Kuchli ko‘rinish: alohida o‘qish modeli (projection), event bilan sinxron — lekin eventual consistency narxi bilan.
- CQRS — spektr: kod ajratishdan alohida omborgacha. Kichikdan boshla, oddiy CRUD uchun ishlatma.
Bu mavzuning Deep/Senior qatlami (read model rebuild/replay, bir nechta maxsus projection, projection idempotency/izchilligi, o‘qish va yozishni mustaqil masshtablash, eventual consistency UX naqshlari, Event Sourcing bilan birlashtirish, concurrency/command validatsiya) kerak bo‘lsa — ayting.
Yoki keyingi mavzu: “10-mavzu: Event Sourcing” — CQRS’ning tabiiy jufti.