Tizim dizayni kursi · 15-mavzu · DEEP / SENIOR (oxirgi)
TuzdiDinMuhammad

Clean Architecture — Senior chuqurlik & kurs yakuni

Bu — 15-mavzuning 2-qatlami va butun kursning yakuni. Core darslikni o‘qib bo‘lgan deb hisoblanadi. Bu yerda eng chuqur narsalar: SOLID chuqur, DDD taktik naqshlar (aggregate/bounded context/domain events), anemic vs rich model, mapping, screaming architecture, use case chegaralari, qatlamlar bo‘yicha test va qachon qoidalarni buzish — junior’dan seniorgacha.

Daraja: Senior · Kurs yakuniMavzular: SOLID · DDD · rich model · testOld shart: 15-mavzu (Core) + butun kurs

Bu qatlamda nima bor

Core darslik asoslarni berdi. Bu Deep qatlam senior darajadagi chuqur mexanikani ochadi: SOLID prinsiplari chuqur, DDD taktik naqshlar (Entity/VO/Aggregate, Bounded Context, Domain Events), anemic vs rich domain model, mapping strategiyalari, screaming architecture va use case chegaralari, qatlamlar bo‘yicha test strategiyasi hamda qachon qoidalarni ongli buzish. Oxirida — butun 15 mavzuli kursning yakuni.

01
Deep · SOLID

SOLID chuqur

SRP·OCP·LSP·ISP·DIP
🧱

Core’da DIP’ni ko‘rdik — u SOLID’ning "D"si. Senior beshtasini ham chuqur biladi, chunki Clean Architecture aynan SOLID’ning tizimli qo‘llanilishi.

PrinsipMohiyat
S — Single ResponsibilitySinf bitta sabab uchun o‘zgarsin (bitta "aktyor"ga xizmat qilsin) — controller, biznes, DB bir joyda emas
O — Open-ClosedKengaytirishga ochiq, o‘zgartirishga yopiq — yangi xatti-harakat qo‘shishda eski kodni buzmaysan
L — Liskov SubstitutionBola sinf ota o‘rniga muammosiz qo‘yilsin — adapter port shartnomasini buzmasin
I — Interface SegregationKichik, fokuslangan interfeyslar — mijoz keraksiz metodlarga bog‘lanmasin
D — Dependency InversionAbstraksiyaga bog‘lan (Core’da — arxitekturaning yuragi)
// ISP — kichik, fokuslangan interfeyslar (bitta ulkan emas)
interface OrderReader { findById(id: string): Promise<Order | null>; }
interface OrderWriter { save(order: Order): Promise<void>; }
// use case faqat KERAKLISIGA bog'lanadi -> keraksiz metodlarga bog'liq emas

// OCP — yangi to'lov usuli QO'SHISH uchun mavjud kodni O'ZGARTIRMAYSAN
interface PaymentMethod { pay(amount: Money): Promise<void>; }
class CardPayment  implements PaymentMethod { /* ... */ }
class ClickPayment implements PaymentMethod { /* ... */ }   // yangi -> qo'shildi, eski buzilmadi
Birga ishlaydi

Clean Architecture — bu prinsiplarning natijasi: DIP (portga bog‘lan) + ISP (kichik portlar) + OCP (adapter qo‘shib kengaytir) birga Ports & Adapters tuzilmasini hosil qiladi. SRP qatlamlarni ajratadi, LSP adapter’lar almashtirilishini kafolatlaydi. SOLID’ni tushunsang — arxitekturani "nega" shundayligini tushunasan.

02
Deep · DDD

Entity, Value Object, Aggregate

aggregate · invariant
🧩

Domain qatlamini "sof" deb aytdik — lekin uni qanday modellashtirish kerak? DDD (Domain-Driven Design) taktik naqshlar aynan shu haqda.

Entity va Value Object

  • Entity: o‘ziga xoslikka (identity) ega, vaqt bilan o‘zgaradi. Ikki buyurtma barcha maydonlari bir xil bo‘lsa ham — ID’si boshqa bo‘lsa, boshqa (Order, User).
  • Value Object: o‘ziga xoslik yo‘q, qiymat bo‘yicha teng, o‘zgarmas (Money, Address, DateRange). 100 so‘m — har doim 100 so‘m.
// VALUE OBJECT — o'ziga xoslik yo'q, qiymat bo'yicha teng, O'ZGARMAS (immutable)
export class Money {
  constructor(readonly amount: number, readonly currency: string) {
    if (amount < 0) throw new Error("Manfiy summa bo'lmaydi");   // invariant konstruktorda
    Object.freeze(this);
  }
  add(o: Money): Money {                          // MUTATSIYA yo'q -> yangi obyekt
    if (this.currency !== o.currency) throw new Error("Valyuta mos emas");
    return new Money(this.amount + o.amount, this.currency);
  }
}

Aggregate — izchillik chegarasi

Aggregate — birga o‘zgaradigan entity/VO’lar to‘plami, bitta root bilan. Tashqi kod faqat rootga murojaat qiladi; invariantlar (qoidalar) root ichida saqlanadi:

// AGGREGATE — izchillik chegarasi; ROOT orqali kirasan, invariantlar ICHKARIDA saqlanadi
export class Order {                              // <-- Aggregate Root
  private items: OrderItem[] = [];
  private constructor(readonly id: OrderId, private status: OrderStatus) {}

  addItem(product: ProductId, qty: number) {      // faqat root orqali o'zgaradi
    if (this.status !== "draft") throw new Error("Faqat draft'ga qo'shiladi"); // invariant
    this.items.push(new OrderItem(product, qty));
  }
  // boshqa aggregate'ga OBYEKT bilan emas, ID bilan murojaat: customerId, productId
}
Order aggregate (chegara) Order (root) OrderItem Product (boshqa agg.) faqat ID bilan
Aggregate: root orqali kirasan; boshqa aggregate’ga ID bilan bog‘lanasan
Kurs bilan bog‘lanish

Aggregate — tranzaksiya chegarasi: bitta aggregate = bitta mahalliy tranzaksiya (13-mavzu). U tabiiy sharding birligi ham (6-mavzu — aggregate ID = shard key). To‘g‘ri aggregate chegaralari ko‘p taqsimlangan tranzaksiya muammosini oldindan yo‘qotadi.

03
Deep · DDD

Bounded Context va Domain Events

bounded context
🗺️

Taktik naqshlar "ichki" edi. Bounded Context — strategik: katta tizimni mustaqil modellarga ajratadi. Bu to‘g‘ridan-to‘g‘ri mikroservis chegaralariga (1-mavzu) ulanadi.

Bounded Context — model chegarasi

Bir xil so‘z turli kontekstda turli narsani bildiradi. "Order" Sotuv kontekstda — savat, narx, chegirma; Yetkazib berish kontekstda — manzil, og‘irlik, kuryer. Ularni bitta ulkan modelga tiqishtirmaslik kerak — har kontekst o‘z modeliga ega:

Sotuv konteksti Order = savat, narx, chegirma Yetkazish konteksti Order = manzil, og‘irlik, kuryer event
Har bounded context — o‘z modeli; ular event orqali gaplashadi

Domain Events — kontekstlarni bog‘lash

Domain event — domenda yuz bergan muhim voqea (OrderConfirmed). U aggregate va kontekstlarni bo‘shashtiradi: Sotuv "tasdiqlandi" eventini chiqaradi, Yetkazish uni tinglaydi — bir-birini to‘g‘ridan-to‘g‘ri bilmaydi:

// DOMAIN EVENT — domenda muhim narsa yuz berdi (aggregate ichida qayd qilinadi)
class Order {
  confirm() {
    if (this.status !== "draft") throw new Error("Faqat draft tasdiqlanadi");
    this.status = "confirmed";
    this.record(new OrderConfirmed(this.id, this.total));   // event ro'yxatga olindi
  }
}
// use case commit qilganda -> event'lar publish qilinadi (3, 10-mavzu bilan ulanadi)
Butun kurs birlashadi

Bounded context = mikroservis chegarasi (1-mavzu). Domain event = event-driven (3-mavzu) va event sourcing (10-mavzu) ning domen ildizi. Ya’ni bu naqshlar tasodifan emas — ular yaxshi domen dizaynidan tabiiy o‘sib chiqadi.

04
Deep · Model

Anemic vs Rich domain model

rich model
🫀

Clean Architecture domen sof bo‘lsin deydi — lekin sof degani boy (rich) degani. Ko‘p loyiha bu yerda adashib, anemik modelga tushadi.

// ANEMIC (anti-pattern) — entity shunchaki ma'lumot; mantiq servisda (protsedural)
class Order { status: string; }                                    // faqat data
class OrderService {
  cancel(o: Order) {
    if (o.status === "confirmed") throw new Error("...");           // qoida TASHQARIDA
    o.status = "cancelled";
  }
}

// RICH — entity O'Z qoidalarini o'zi biladi (haqiqiy OO, clean arch maqsadi)
class Order {
  cancel() {
    if (this.status === "confirmed") throw new Error("...");        // qoida ICHKARIDA
    this.status = "cancelled";
  }
}
Anemic modelRich model
EntityFaqat ma’lumot (getter/setter)Ma’lumot + xatti-harakat
Mantiq qayerdaServislarda (protsedural)Entity ichida (OO)
QoidalarTarqoq, takrorlanadiBir joyda, himoyalangan
BahoFowler: anti-patternClean arch maqsadi
Nega rich muhim

Anemik modelda order.status = "cancelled" ni istalgan joyda qilish mumkin — qoida chetlab o‘tiladi, takrorlanadi, buziladi. Rich modelda bekor qilishning yagona yo‘liorder.cancel(), va u qoidani majburlaydi. Domen shunda haqiqatan "biznes qoidalarini himoya qiluvchi qal’a" bo‘ladi.

Pragmatik istisno

Oddiy CRUD (qoida deyarli yo‘q — shunchaki saqlash/o‘qish)da anemik model maqbul — sun’iy "rich" qilish ortiqcha. Rich model haqiqiy biznes qoidalari bor domen uchun.

05
Deep · Mapping

Mapping strategiyalari

mapper · 3 model
🔀

Domenni sof tutishning narxi bor: mapping. Uch xil model bor — DB, domen, API — va ular orasida tarjima kerak.

Prisma model (DB) map Domen entity map DTO (API)
Uch model, mapper orqali tarjima — har biri o‘z maqsadiga xizmat qiladi
// MAPPER — qatlamlar orasida tarjima (domen sof qoladi, Prisma/DTO'ga bog'lanmaydi)
class OrderMapper {
  // DB row -> domen (rehydrate)
  static toDomain(row: PrismaOrder): Order {
    return Order.rehydrate(row.id, row.status, row.items);
  }
  // domen -> DB shakli (persistence)
  static toPersistence(o: Order): Prisma.OrderUpdateInput {
    return { status: o.status };
  }
  // domen -> API javob (DTO)  -> ichki modelni tashqariga oshkor qilmaydi
  static toDto(o: Order): OrderDto {
    return { id: o.id, status: o.status, total: o.total.amount };
  }
}

Nega uch model? Prisma model DB sxemasiga bog‘langan; DTO API shakliga; ikkalasi ham o‘zgarishi mumkin. Domen ularga bog‘lansa — DB yoki API o‘zgarganda domen ham buziladi. Mapper domenni himoya qiladi.

Pragmatik muvozanat

Mapping — boilerplate, bu haqiqat. Kichik/oddiy loyihada persistence va domen modelini birlashtirish (bitta model) maqbul — mapping narxi foydasidan oshib ketsa. Murakkab/uzoq loyihada esa ajratish o‘zini oqlaydi: DB migratsiyasi domenga tegmaydi. Qaror — kontekstga bog‘liq.

06
Deep · Struktura

Screaming architecture va use case chegaralari

screaming · use case
📣

Papka tuzilmasi ham arxitekturaning bir qismi. Screaming Architecture (Uncle Bob): tuzilma domenni baqirsin, framework’ni emas.

Domen bo‘yicha, texnik qatlam bo‘yicha emas

Ko‘p loyiha papkalarni controllers/, services/, models/ deb ajratadi — bu frameworkni baqiradi. Yaxshiroq: domen bo‘yicha (orders/, payments/) — loyihaga qarab "bu buyurtma tizimi" ekanini darhol bilasan:

src/
  orders/                 # <-- domen "baqiradi", framework emas
    domain/         # Order (entity), Money (VO), OrderRepository (port)
    application/    # CancelOrderUseCase, ConfirmOrderUseCase
    infrastructure/ # PrismaOrderRepository, OrderController
  payments/
    domain/  application/  infrastructure/
  shipping/
    domain/  application/  infrastructure/

Use case chegaralari

Har use case — bitta ilova amali, aniq kirish va chiqish chegarasi bilan. Kirish (request model), chiqish (response model), bitta execute(). Bu use case’larni fokuslangan va mustaqil tutadi:

  • Bitta use case = bitta amal (CancelOrder, ConfirmOrder — alohida, "OrderService" ichida hammasi emas).
  • Aniq input/output — use case HTTP/DB shaklini bilmaydi, o‘z request/response modeliga ega.
  • Controller — HTTP’ni use case input’iga tarjima qiladi, boshqa hech narsa.
Foyda

Bu tuzilma bilan yangi dasturchi loyihaga kelib, orders/application/ni ochib — tizim nima qila olishini (barcha use case’lar ro‘yxati) darhol ko‘radi. Biznes imkoniyatlari kod tuzilmasidan "baqirib" turadi.

07
Deep · Test

Qatlamlar bo‘yicha test strategiyasi

test piramidasi
🧪

Clean Architecture’ning eng katta amaliy foydasi — test. Har qatlam o‘z uslubi bilan sinaladi va test piramidasiga tabiiy mos keladi.

// DOMAIN — sof unit test (tez, mock YO'Q, qoidani to'g'ridan-to'g'ri sinaydi)
const order = Order.create();
order.confirm();
expect(() => order.cancel()).toThrow("Tasdiqlangan buyurtma bekor bo'lmaydi");

// USE CASE — mock port (in-memory repository), orkestratsiyani tekshiradi
const repo = new InMemoryOrderRepository([draftOrder]);
await new CancelOrderUseCase(repo).execute(draftOrder.id);
expect((await repo.findById(draftOrder.id)).status).toBe("cancelled");

// ADAPTER — integratsiya test (HAQIQIY DB, testcontainers) — mapping to'g'riligini
E2E (kam) Integratsiya — adapter Unit — domen + use case (ko‘p, tez)
Test piramidasi qatlamlarga mos: ko‘p domen/use-case, oz integratsiya, kam e2e
QatlamTest turiXususiyat
DomainSof unitTez, mock yo‘q, qoidani to‘g‘ridan-to‘g‘ri
Use CaseUnit + mock portIn-memory repo, orkestratsiya tekshiriladi
AdapterIntegratsiyaHaqiqiy DB (testcontainers), mapping
Nega bu katta yutuq

An’anaviy kodda biznes qoidasini sinash uchun DB + HTTP + framework kerak — sekin, mo‘rt, kam. Clean Architecture’da domen va use case testlari millisekundda, hech qanday infratuzilmasiz ishlaydi. Natijada ko‘p, ishonchli test — bu esa qo‘rqmasdan refactoring qilish erkinligini beradi.

08
Deep · Yakun

Qachon qoidalarni buzish + kurs yakuni

🎚️

Yakuniy senior saboq: qoidalar maqsadga xizmat qiladi, aksincha emas. Qachon ularni ongli ravishda buzishni bilish — haqiqiy yetuklik.

Qachon qoidalarni buzish mumkin

  • Oddiy CRUD: qoida deyarli yo‘q ilovada to‘liq qatlam/mapping — ortiqcha. Prismani use case’ga to‘g‘ridan-to‘g‘ri ishlatish maqbul.
  • Prototip / MVP: tezlik muhimroq; keyin domen murakkablashsa — qatlamla.
  • Kichik jamoa/skript: bilvositalik narxi foydasidan oshsa — soddaroq tuzilma.
Asosiy tamoyil

Clean Architecture — dogma emas, vosita. Maqsad: saqlanadigan, testlanadigan, o‘zgarishga chidamli kod. Agar biror qoida sizning kontekstingizda qiymat qo‘shmay, faqat narx keltirsa — uni ongli ravishda buzing. Yomon dasturchi qoidani bilmasdan buzadi; yaxshi dasturchi bilib buzadi; senior — qachon buzish, qachon saqlashni biladi.

🎓 Kurs yakuni — 15 mavzu, Core + Deep

Siz tizim dizaynining to‘liq yo‘lini bosib o‘tdingiz:
Struktura (1–3): Mikroservis, Modular Monolith, Event-Driven
Masshtab (4–8): Caching, Load Balancing, Sharding, Replication, API Gateway
Ma’lumot naqshlari (9–10): CQRS, Event Sourcing
Nazariya & ko‘rinish (11–12): CAP Theorem, Observability
Ishonchlilik (13–14): Distributed Transactions, Resilience Engineering
Tashkil (15): Clean Architecture — hammasini birlashtiruvchi

Har mavzu Core (tushunish) va Deep/Senior (chuqur mexanika) qatlamlari bilan. Endi sizda junior’dan senior gacha mustahkam, amaliy (NestJS) poydevor bor.

🚀 Tabriklaymiz — butun kurs tugadi! 15 mavzu × 2 qatlam. Bilim — qurishda mustahkamlanadi: bu naqshlarni real loyihalaringizda qo‘llang, va tizimlaringiz uzoq, o‘zgaruvchan, lekin buzilmasdan yashasin.

Omad, Dinmuhammad! Savollar bo‘lsa — har doim shu yerdaman.