Tizim dizayni kursi · 9-mavzu
TuzdiDinMuhammad

CQRS — o‘qish va yozishni ajratish

Bitta model ham o‘qish, ham yozishga xizmat qilganda siqiladi — chunki ularning ehtiyojlari tubdan farq qiladi. CQRS ularni ikki alohida modelga (Command va Query) ajratadi. Noldan boshlab asosiy g‘oya, NestJS @nestjs/cqrs implementatsiyasi, alohida o‘qish modeli (projection) va eventual consistency kelishuvini o‘rganamiz.

Stack: NestJS · @nestjs/cqrs · PrismaMavzular: command · query · read model · projectionBog‘liq: 3-mavzu (Event-Driven), 10 (Event Sourcing)

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.

01
CQRS · qism 1

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.

Muammo

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.

Yechim — CQRS

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.

Asosi — CQS prinsipi

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.

02
CQRS · qism 2

Asosiy g‘oya — ikki tomonga ajratish

CQRS tizimni ikki tomonga ajratadi — har biri mustaqil optimallashtiriladi.

Ilova Command Query Write model (yozuv)validatsiya · domen · normalizatsiya Read model (o‘qish)tez · denormalizatsiya · UI shakli yozuv ombori o‘qish ombori
Ikki tomon: yozuv (toza, izchil) va o‘qish (tez, UI’ga moslangan)

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.

03
CQRS · qism 3

Command va Query — aniq farq

Ikki tomonni aniq farqlash kerak — ular butunlay boshqacha xulq-atvorga ega:

Command (yozuv)Query (o‘qish)
MaqsadHolatni o‘zgartirishMa’lumot qaytarish
NomiBuyruq: CreateOrder, CancelOrderSavol: GetUserOrders
QaytaradiHech narsa / tasdiq (ack)UI’ga moslangan DTO
Yo‘liValidatsiya + biznes logika orqaliDomen logikasini chetlab, to‘g‘ridan-to‘g‘ri
Side-effectBor (yozadi)Yo‘q (faqat o‘qiydi)
Asosiy farq

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.

04
CQRS · qism 4

NestJS implementatsiya (@nestjs/cqrs)

CommandBus · QueryBus

NestJS’da CQRS uchun rasmiy modul bor: @nestjs/cqrsCommandBus, 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));
  }
}
Nima yutamiz

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.

05
Chuqurlashtirilgan mavzu

Alohida o‘qish modeli (projection)

read model · projection
🪞

CQRS’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',
      },
    });
  }
}
Write modelnormalizatsiya event Projectiono‘qish modelini yangilaydi Read modeldenormalizatsiya · tez
Yozuv → event → projection → o‘qishga tayyor denormalizatsiya qilingan model
Read model qayerda

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).

06
Chuqurlashtirilgan mavzu

Eventual consistency va kelishuvlar

eventual consistency

Alohida o‘qish ombori kuchli, lekin u narx bilan keladi: read model yozuv modelidan biroz orqada qoladi — pirovard izchillik (eventual consistency).

Asosiy kelishuv

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.

Yechimlar

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.

Muhim nuans

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.

07
Qaror

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
Amaliy qoidalar

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).

08
Yakuniy

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/cqrsCommandBus/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.