IlmHamroh
Dasturlash kitobi (eski nashr)/9-QISM — Arxitektura3/10-dars27 daqiqa
Mundarija (45)

Clean Architecture, Layered va Hexagonal arxitektura — NestJS'da (9.3-bob)

9-QISM — Arxitektura va ilg'or backend · 3-mavzu


1. Kirish va motivatsiya

SOLID 9.1-bob va design pattern'larni 9.2-bob bildik. Endi ularni butun ilova darajasida tashkil qiladigan arxitektura uslublarini — Layered, Hexagonal (Ports & Adapters), Clean Architecture — o'rganamiz. Bu — kichik darajadagi tamoyillarni (SOLID) katta strukturaga aylantiradi: ilovani qatlamlarga bo'lish, bog'liqlik yo'nalishini boshqarish, biznes mantiqni texnologiyadan ajratish. Bu — kichik loyihada sezilmaydi, lekin katta, uzoq yashaydigan, jamoaviy loyihada — hayot-mamot masala.

Asosiy g'oya — biznes mantiq markazda, texnologiya chekkada. Ilovaning yuragi — biznes qoidalari (buyurtma qanday yaratiladi, chegirma qanday hisoblanadi) — DB, framework, API'dan mustaqil bo'lishi kerak. Bugun TypeORM, ertaga Prisma; bugun Express, ertaga Fastify; bugun REST, ertaga GraphQL — lekin biznes mantiq o'zgarmaydi. Clean Architecture buni ta'minlaydi: tashqi narsalar (DB, framework) — almashtiriladigan "detal"; biznes mantiq — barqaror "yadro".

Bu bob: nega arxitektura, Layered (qatlamli), Hexagonal (Ports & Adapters), Clean Architecture (qatlamlar, bog'liqlik qoidasi — Dependency Rule), entity/use-case/adapter, va NestJS'da amaliy tatbiq. Bu bob 9.1 (SOLID — ayniqsa DIP), 9.2 (Repository, Adapter), 8 (NestJS) ni butun ilova strukturasiga birlashtiradi. Bu — pragmatik (har loyihaga to'liq Clean kerak emas — balans). Bu — senior arxitektura bilimi.

O'xshatish: Clean Architecture — shahar rejalashtirish. Shahar markazida — eng muhim, barqaror narsa (biznes mantiq — hokimiyat, asosiy infratuzilma). Chekkada — almashtiriladigan narsalar (DB, framework, API — do'konlar, transport — vaqti-vaqti bilan yangilanadi). Bog'liqlik qoidasi — chekka markazga qarab quriladi (do'kon shaharga moslashadi, shahar do'konga emas). Markazni o'zgartirmasdan chekka (DB, framework) almashtiriladi. Yomon shahar — markaz va chekka aralash (biznes mantiq DB ichida — bir narsani o'zgartirsangiz, hammasi buziladi). Clean shahar — markaz himoyalangan, chekka erkin almashinadigan.

Nega muhim?

  • Texnologiyadan mustaqillik — DB/framework almashtiriladigan.
  • Test qilinadigan — biznes mantiq izolyatsiyada (DB'siz).
  • Uzoq yashash — katta loyiha yillar davom etadi.
  • Toza chegaralar — jamoa, kengaytirish, tushunish.

2. Nazariya — chuqur tushuntirish

2.1. Nega arxitektura (muammo)

text
  ARXITEKTURASIZ (hammasi aralash):
  Controller → DB so'rov to'g'ridan; biznes mantiq controller'da;
  framework hamma joyda → DB almashtirsa, hamma buziladi; test qiyin

  ARXITEKTURA BILAN (qatlamlar):
  Controller → Use Case → Domain ← Repository (interfeys)
                                    ↑ implementatsiya (DB)
  → biznes mantiq markazda, mustaqil; DB/framework almashtiriladigan; test oson

Nega arxitektura: arxitekturasiz — biznes mantiq, DB, framework aralash (bir o'zgarish hammani buzadi, test qiyin, tushunish murakkab). Arxitektura bilan — qatlamlar (har biri aniq vazifa), biznes mantiq markazda (mustaqil). Bu — SOLID'ni 9.1-bob butun ilovaga qo'llash. Katta loyiha uchun zarur.

2.2. Layered Architecture (qatlamli — klassik)

text
  ┌─────────────────────────────┐
  │  Presentation (Controller)  │  ← HTTP, GraphQL, CLI 8.1-bob
  ├─────────────────────────────┤
  │  Business/Service           │  ← biznes mantiq 8.1-bob
  ├─────────────────────────────┤
  │  Data Access (Repository)   │  ← DB so'rov 8.3-bob
  ├─────────────────────────────┤
  │  Database                   │
  └─────────────────────────────┘
  Yuqori → past chaqiradi (controller → service → repo → DB)

Layered (klassik — eng keng) — gorizontal qatlamlar: presentation (controller — 8.1), business (service), data access (repository — 8.3), database. Har qatlam pastdagini chaqiradi. Bu — NestJS'da qo'llaniladigan odatiy tuzilma (8.1: 2.7 — controller/service/repo)! Oddiy, tushunarli. Kamchilik: biznes mantiq DB'ga bog'liq bo'lib qolishi mumkin (qatlamlar yuqoridan pastga).

2.3. Bog'liqlik muammosi (Layered kamchiligi)

text
  Layered'da odatda:
  Service → Repository (konkret) → DB
  → biznes mantiq DB'ga bog'liq (DB o'zgarsa, service ta'sirlanadi)

  YECHIM (DIP — 9.1: 2.9):
  Service → Repository INTERFEYSI ← Repository implementatsiyasi → DB
  → biznes mantiq interfeysga tayanadi (DB detali pastda, almashtiriladigan)
  → Bu — Hexagonal/Clean'ning asosiy g'oyasi

Layered kamchiligi: odatiy layered'da service → konkret repository → DB (biznes mantiq DB'ga bog'liq). Yechim — DIP (9.1: 2.9): service repository interfeysiga tayanadi, implementatsiya pastda (almashtiriladigan). Bu — bog'liqlikni "teskari aylantirish" (dependency inversion) — Hexagonal/Clean'ning markaziy g'oyasi. Biznes mantiq DB detalini bilmaydi.

2.4. Hexagonal Architecture (Ports & Adapters)

text
  ┌──────────────────────────────────────────┐
  │           ADAPTERS (tashqi)               │
  │   ┌────────────────────────────────┐     │
  │   │   PORTS (interfeyslar)         │     │
  │   │   ┌──────────────────────┐     │     │
  │   │   │   CORE (biznes mantiq) │   │     │
  │   │   │   hech narsaga bog'liq emas│ │     │
  │   │   └──────────────────────┘     │     │
  │   └────────────────────────────────┘     │
  └──────────────────────────────────────────┘
  Tashqi (DB, API, UI) → port (interfeys) → core
  Core → port (interfeys) → adapter (implementatsiya)

Hexagonal (Ports & Adapters) — biznes mantiq markazda (core — hech narsaga bog'liq emas). Port — interfeys (core va tashqi dunyo shartnomasi). Adapter — port implementatsiyasi (DB, API, UI — tashqi). Tashqi narsalar port orqali ulanadi (almashtiriladigan). Biznes mantiq markazda joylashadi va hech narsaga bog'lanmaydi. DB/framework — adapter (detal). Hexagonal — DIP'ni butun ilovaga.

Nega "olti burchak" (hexagon)? Bu — soni emas, ramz. Alistair Cockburn (uslub muallifi) qatlamli diagrammadagi "yuqori/past" tushunchasidan qochish uchun simmetrik shakl tanladi: core atrofidagi barcha tomonlar teng huquqli. Har burchak — bitta port (biror ehtiyoj yoki imkoniyat). Asosiy g'oya — ilovaning ichi (core) va tashqarisi (adapter'lar) o'rtasida aniq chegara. Ichkariga faqat port orqali kiriladi, tashqariga faqat port orqali chiqiladi. Shu chegara tufayli ilovani haqiqiy tashqi dunyosiz (DB'siz, HTTP'siz) ishga tushirib, test qilib bo'ladi — port o'rniga soxta (mock/in-memory) adapter qo'yiladi. Bu — "test qatlam" tushunchasining amaliy asosi: birlik testlar core'ni driving port orqali chaqiradi va driven port'larni soxtalashtiradi.

2.5. Port va Adapter (chuqurroq)

text
  PORT turlari:
  - Driving port (inbound): tashqi → core (use-case interfeysi — API chaqiradi)
  - Driven port (outbound): core → tashqi (repository interfeysi — DB'ga)

  ADAPTER turlari:
  - Driving adapter: controller (HTTP), CLI, bot 8.12-bob — core'ni chaqiradi
  - Driven adapter: TypeORM repo 8.3-bob, email service 8.10-bob — port'ni amalga oshiradi

  → Core port'larni belgilaydi; adapter'lar ularni amalga oshiradi (almashtiriladigan)

Port/Adapter: port — interfeys (driving — tashqi→core use-case; driven — core→tashqi repository). Adapter — implementatsiya (driving — controller/bot; driven — TypeORM/email). Core port'larni belgilaydi (men shunga muhtojman), adapter ularni amalga oshiradi. Almashtirish: TypeORM→Prisma faqat adapter o'zgaradi (core tegmaydi). DIP amalda (9.1: 2.9).

2.6. Clean Architecture (qatlamlar — Uncle Bob)

text
  ┌────────────────────────────────────────────┐
  │  Frameworks & Drivers (DB, web, tashqi)    │  ← eng tashqi (detal)
  │  ┌──────────────────────────────────────┐  │
  │  │  Interface Adapters (controller, repo)│ │
  │  │  ┌────────────────────────────────┐  │  │
  │  │  │  Use Cases (ilova mantig'i)    │  │  │
  │  │  │  ┌──────────────────────────┐  │  │  │
  │  │  │  │  Entities (biznes qoida) │  │  │  │  ← eng ichki (yadro)
  │  │  │  └──────────────────────────┘  │  │  │
  │  │  └────────────────────────────────┘  │  │
  │  └──────────────────────────────────────┘  │
  └────────────────────────────────────────────┘

Clean Architecture (Uncle Bob) — konsentrik qatlamlar: Entities (eng ichki — biznes qoidalar), Use Cases (ilova mantig'i), Interface Adapters (controller, repository, presenter), Frameworks & Drivers (eng tashqi — DB, web, tashqi API). Ichki — barqaror (biznes); tashqi — almashinadigan (texnologiya). Hexagonal va Onion'ning umumlashtirilgani.

2.6b. Onion Architecture (piyoz — Jeffrey Palermo)

text
  Onion — konsentrik halqalar (piyoz qatlamlari):

  ┌──────────────────────────────────────────────┐
  │  Infrastructure + UI + Tests (eng tashqi)     │  ← DB, framework, test
  │  ┌────────────────────────────────────────┐   │
  │  │  Application Services (use-case)        │   │
  │  │  ┌──────────────────────────────────┐   │   │
  │  │  │  Domain Services                 │   │   │
  │  │  │  ┌────────────────────────────┐  │   │   │
  │  │  │  │  Domain Model (entity)     │  │   │   │  ← markaz (yadro)
  │  │  │  └────────────────────────────┘  │   │   │
  │  │  └──────────────────────────────────┘   │   │
  │  └────────────────────────────────────────┘   │
  └──────────────────────────────────────────────┘
  Bog'liqlik markazga qaraydi (tashqi → ichki)
  Interfeyslar (repository port) — ichki halqada; impl — tashqida

Onion Architecture (Jeffrey Palermo, 2008) — Hexagonal va Clean orasidagi ko'prik. Markazda Domain Model (entity — biznes holati va qoidasi), atrofida Domain Services (bir nechta entity ustidagi biznes mantiq), keyin Application Services (use-case — koordinatsiya), eng tashqida Infrastructure/UI/Tests. Kalit farq: barcha interfeyslar (repository port) ichki halqalarda e'lon qilinadi, implementatsiya esa tashqi halqada — shu tufayli bog'liqlik doim markazga qaraydi. Onion — Clean'ga juda yaqin (Uncle Bob uni ochiq tan olgan); amalda uch nomni (Hexagonal, Onion, Clean) bir oilaning shevalari deb qarash mumkin: hammasi bir maqsadga xizmat qiladi — biznes markazda, bog'liqlik ichkariga.

Uch uslub — bir g'oya (taqqoslash):

Jihat Hexagonal Onion Clean
Muallif Cockburn (2005) Palermo (2008) Martin (2012)
Shakl olti burchak (port'lar) konsentrik halqalar konsentrik halqalar
Markaz core (biznes) domain model entities
Chegara nomi port / adapter interface / infra port / adapter
Asosiy qoida ichkariga bog'liqlik ichkariga bog'liqlik Dependency Rule

Amalda ularni aralashtirib ishlatasiz (masalan: Clean'ning qatlam nomlari + Hexagonal'ning port/adapter atamalari). Muhimi — nom emas, tamoyil: biznes texnologiyaga bog'lanmaydi.

2.7. Dependency Rule (eng muhim qoida)

text
  ❗ DEPENDENCY RULE — bog'liqlik FAQAT ichkariga yo'naladi:
  tashqi qatlam → ichki qatlam (HECH QACHON aksincha emas)

  Entities — hech narsani bilmaydi (DB, framework, use-case)
  Use Cases — faqat Entities'ni biladi (DB emas)
  Adapters — Use Cases + Entities'ni biladi
  Frameworks — hammasini biladi (lekin hech kim uni bilmaydi)

  → Ichki qatlam tashqini BILMAYDI (DIP — interfeys orqali)

Dependency Rule (Clean'ning yuragi): bog'liqlik faqat ichkariga yo'naladi (tashqi → ichki, hech qachon aksincha). Entity hech narsani bilmaydi (DB/framework); use-case faqat entity; adapter use-case+entity. Ichki qatlam tashqini bilmaydi (DIP — interfeys orqali — 9.1: 2.9). Bu — biznes mantiqni texnologiyadan himoya qiladi (DB o'zgarsa, entity/use-case tegmaydi).

2.8. Entities (biznes qoidalar — yadro)

typescript
// Entity — sof biznes qoida (DB, framework BILMAYDI)
class Order {
  constructor(
    public readonly id: string,
    private items: OrderItem[],
    private status: OrderStatus,
  ) {}

  // Biznes qoida (entity ichida — behavior-oriented)
  jamiSumma(): number {
    return this.items.reduce((s, i) => s + i.narx * i.miqdor, 0);
  }
  bekorQilish(): void {
    if (this.status === "yetkazildi") {
      throw new Error("Yetkazilgan buyurtma bekor qilinmaydi");   // biznes qoida
    }
    this.status = "bekor";
  }
}
// ❗ Bu klassda DB, NestJS, HTTP yo'q — sof biznes mantiq

Entity — sof biznes qoida (DB/framework'ni bilmaydi — eng ichki). Biznes mantiq entity ichida (behavior-oriented — bekorQilish qoidasi entity'da, service'da emas). Test oson (DB kerak emas). Hech qanday @Entity/@Column 8.3-bob — u infratuzilma (alohida). Domen entity ≠ ORM entity (toza Clean'da ajratiladi). Biznes qoidalar yashaydi.

2.9. Use Cases (ilova mantig'i)

typescript
// Use Case — bitta amaliy stsenariy (biznes oqimi)
class CreateOrderUseCase {
  constructor(
    private orderRepo: OrderRepository,               // PORT (interfeys — 2.5)
    private paymentGateway: PaymentGateway,           // PORT
    private eventBus: EventBus,                        // PORT
  ) {}

  async bajar(input: CreateOrderInput): Promise<Order> {
    const order = new Order(/* ... */);               // entity (2.8)
    if (!(await this.paymentGateway.tolov(order.jamiSumma()))) {
      throw new Error("To'lov amalga oshmadi");
    }
    await this.orderRepo.saqla(order);                // port orqali (DB detalini bilmaydi)
    this.eventBus.elon("buyurtma_yaratildi", order);  // event (9.7)
    return order;
  }
}

Use Case — bitta amaliy stsenariy (buyurtma yaratish, login). Biznes oqimini boshqaradi (entity + port'lar). Faqat interfeyslarga (port) tayanadi — DB/framework'ni bilmaydi (DIP). Har use-case — bir vazifa (SRP — 9.1). NestJS'da service yoki alohida use-case klass. Bu — ilovaga xos biznes mantiq (application-specific): entity biznes qoidasini, use-case shu qoidalarni maqsadga yo'naltirilgan oqimga birlashtirishni bildiradi. Bu — Clean'ning amaliy qatlami.

2.9b. Input port, Output port, Interactor (Clean'ning ichki mexanikasi)

text
  Uncle Bob use-case'ni uch bo'lakka ajratadi:

  Controller ──▶ [Input Port]  ──▶  Interactor  ──▶  [Output Port] ──▶ Presenter
   (adapter)     (interfeys)      (use-case impl)     (interfeys)      (adapter)
                                        │
                                        ▼
                              Repository port (driven)

  - Input port  — use-case KIRISH interfeysi (controller shuni chaqiradi)
  - Interactor  — input port'ning implementatsiyasi (haqiqiy biznes oqimi)
  - Output port — use-case CHIQISH interfeysi (natijani presenter'ga uzatadi)
  - Presenter   — output port impl (natijani UI/HTTP formatiga o'giradi)

  → Controller natijani O'ZI formatlamaydi; use-case output port orqali beradi
  → Bu — use-case'ni HTTP javob shaklidan ham ajratadi (to'liq izolyatsiya)
typescript
// application/ports/create-order.input-port.ts — use-case KIRISHI
export interface CreateOrderInputPort {
  bajar(input: CreateOrderInput): Promise<void>;
}

// application/ports/create-order.output-port.ts — use-case CHIQISHI
export interface CreateOrderOutputPort {
  muvaffaqiyat(order: Order): void;
  tolovXatosi(sabab: string): void;
}

// application/create-order.interactor.ts — INTERACTOR (input port impl)
export class CreateOrderInteractor implements CreateOrderInputPort {
  constructor(
    private orderRepo: OrderRepository,          // driven port
    private payment: PaymentGateway,             // driven port
    private output: CreateOrderOutputPort,       // output port (presenter'ga)
  ) {}

  async bajar(input: CreateOrderInput): Promise<void> {
    const order = Order.yarat(input.userId, input.items);
    if (!(await this.payment.tolov(order.jamiSumma(), input.userId))) {
      return this.output.tolovXatosi("To'lov amalga oshmadi");   // natija — output port orqali
    }
    await this.orderRepo.saqla(order);
    this.output.muvaffaqiyat(order);              // controller emas, presenter formatlaydi
  }
}

Input/Output port va Interactor — Clean'ning eng nozik, ko'pincha o'tkazib yuboriladigan qismi. Input port — use-case'ga kirish interfeysi (controller shunga bog'lanadi, konkret klassga emas — DIP ikki tomonlama). Interactor — input port implementatsiyasi (biznes oqimining o'zi). Output port — natija interfeysi: use-case javobni return qilmaydi, balki output port'ni chaqiradi. Presenter (output port adapter) natijani HTTP/UI formatiga o'giradi. Foydasi: use-case javob shakliga ham bog'lanmaydi (bir use-case REST'da JSON, CLI'da matn, WebSocket'da event chiqaradi — presenter'ni almashtirib). Amalda bu ko'p boilerplate — NestJS loyihalarida ko'pincha use-case return qiladi, presenter esa controller ichida qoladi (soddalashtirilgan Clean). Nazariy to'liq shakl — mana shu; qachon to'liq kerak — juda murakkab, ko'p taqdimot kanalli tizimda.

2.10. Interface Adapters (controller, repository)

typescript
// Adapter — tashqi dunyoni use-case'ga ulaydi
// Driving adapter (controller — HTTP → use-case)
@Controller("orders")
class OrderController {
  constructor(private createOrder: CreateOrderUseCase) {}
  @Post()
  async yarat(@Body() dto: CreateOrderDto) {
    return this.createOrder.bajar(dto);               // use-case'ni chaqiradi
  }
}
// Driven adapter (repository — port implementatsiyasi → DB)
class TypeOrmOrderRepository implements OrderRepository {   // PORT'ni amalga oshiradi
  async saqla(order: Order): Promise<void> {
    // domen entity → ORM entity → DB (8.3)
  }
}

Interface Adapters — tashqi dunyo va use-case orasidagi ko'prik. Controller (driving — HTTP→use-case — 8.1), repository (driven — port implementatsiyasi→DB — 8.3). Bu yerda format o'zgaradi (HTTP DTO ↔ domen, domen ↔ ORM entity). Adapter almashtiriladigan (REST→GraphQL, TypeORM→Prisma). Core tegmaydi.

2.11. Frameworks & Drivers (eng tashqi — detal)

text
  Eng tashqi qatlam — "detal" (almashtiriladigan):
  - DB (PostgreSQL, MongoDB — 6)
  - Web framework (NestJS, Express — 8, 5.6)
  - Tashqi API (Click, Eskiz SMS — 5.18)
  - ORM (TypeORM, Prisma — 6.11-6.13)

  ❗ Uncle Bob: "DB — detal, framework — detal"
  → biznes mantiq ularga bog'liq bo'lmasligi kerak (almashtiriladigan)

Frameworks & Drivers — eng tashqi (detal). DB, web framework, ORM, tashqi API. Uncle Bob: "DB — detal, framework — detal" (biznes mantiqning markaziy qismi emas). Ular almashtiriladigan (PostgreSQL→Mongo, NestJS→Express). Biznes mantiq bularga bog'liq bo'lmasligi kerak. Bu — eng radikal g'oya (odatda DB markazda deb o'ylanadi).

2.11b. Entity vs DTO vs ORM entity (uch model — chalkashlik manbai)

text
  Bir "buyurtma" — uch xil ko'rinishda (qatlamiga qarab):

  1) DOMAIN ENTITY  (domain/)         — biznes qoidali obyekt
     - metodlar bor (bekorQil, jamiSumma), inkapsulyatsiya
     - DB, HTTP, framework BILMAYDI
     - "identifikatsiya"ga ega (id bo'yicha tenglik)

  2) DTO  (presentation/)             — ma'lumot tashuvchi (transfer)
     - faqat maydonlar (metod yo'q), plain obyekt
     - HTTP so'rov/javob shakli (validatsiya dekoratorlari — 8.5)
     - "qiymat"ga ega (maydonlar bo'yicha tenglik)

  3) ORM ENTITY  (infrastructure/)    — DB jadval xaritasi
     - @Entity/@Column 8.3-bob — TypeORM/Prisma bog'liq
     - DB sxemasi shakli (nullable, index, relation)

  → Uchtasini ARALASHTIRISH — eng keng tarqalgan Clean xatosi

Entity ≠ DTO ≠ ORM entity — bu uch tushunchani ajratish Clean'da hal qiluvchi. Domain entity — xatti-harakatli (metodlari bor), biznes qoidasini himoya qiladi, id bo'yicha aynanlashtiriladi (identity). DTO (Data Transfer Object) — sof ma'lumot qutisi (metodsiz), qatlamlar/tarmoq orasida ma'lumot uzatadi; presentation'da HTTP so'rov/javob shakli (CreateOrderDto, OrderResponseDto) bo'lib, validatsiya dekoratorlarini 8.5-bob shu yerda saqlaydi. ORM entity — DB jadvaliga xarita (@Column, relation, index — 8.3). Ular maqsadan farq qiladi, shuning uchun bir klass qilib qo'yish (masalan, domen entity'ga @Column yopishtirish) — qatlam chegarasini buzadi: biznes mantiq DB sxemasiga va HTTP shakliga bir vaqtda bog'lanib qoladi. Mapper (Misol 9) uch model orasida o'giradi. Narx: ko'proq kod. Kichik CRUD'da bir model ishlatib, DTO/ORM/domen farqini kechiktirish mumkin (2.13 — pragmatik balans).

2.11c. Screaming Architecture (loyiha "nima haqida" ekanini baqirsin)

text
  ❌ Framework baqiradigan struktura (nima QILADIGANI noaniq):
  src/
  ├── controllers/    ← "bu NestJS loyihasi" deb baqiradi
  ├── services/
  ├── entities/
  └── modules/        ← lekin loyiha NIMA HAQIDA? — bilinmaydi

  ✅ Domen baqiradigan struktura (nima HAQIDA ekani ravshan):
  src/
  ├── orders/         ← "bu — BUYURTMA tizimi" deb baqiradi
  ├── payments/
  ├── delivery/
  └── inventory/
      → papka nomlari BIZNES sohasini aytadi, framework'ni emas

Screaming Architecture (Uncle Bob) — loyihaning eng yuqori darajadagi papka strukturasi loyiha nima haqida ekanini "baqirishi" kerak, qaysi framework'da yozilganini emas. Kutubxona javonidagi kitob nomi uning mavzusini aytadi, uni chop etgan bosmaxonani emas — shu o'xshatish asosdir. Xuddi shunday, src/ ochilganda ko'ringan narsa controllers/, services/ emas (bu — texnik detal, framework "baqiradi"), balki orders/, payments/, inventory/ (biznes sohasi) bo'lishi kerak. Bu — vertikal bo'linish (feature/domen bo'yicha), gorizontal (texnik rol bo'yicha) emas. Har domen papkasi ichida Clean qatlamlari (domain/application/infrastructure/presentation) joylashadi. Foydasi: yangi dasturchi loyihani ochib darrov nima ustida ishlayotganini tushunadi; bir feature'ni o'zgartirganda faqat bitta papkaga tegiladi (yuqori kohezya, past bog'liqlik). Bu — 2.12 dagi modul strukturasining asosiy tamoyili.

2.12. NestJS'da Clean/Hexagonal (amaliy)

text
  NestJS modul 8.1-bob → Hexagonal'ga tabiiy mos:
  modul/
  ├── domain/              ← CORE (entity, port interfeyslar)
  │   ├── order.entity.ts        (sof biznes — 2.8)
  │   └── order.repository.ts    (PORT interfeys — 2.5)
  ├── application/         ← USE CASES
  │   └── create-order.use-case.ts   2.9-bob
  ├── infrastructure/      ← ADAPTERS (driven)
  │   └── typeorm-order.repository.ts   (port impl — 2.10)
  └── presentation/        ← ADAPTERS (driving)
      └── order.controller.ts    2.10-bob

  Module: { provide: OrderRepository, useClass: TypeOrmOrderRepository }  (DIP — 8.2)

NestJS'da Clean — modul 8.1-bob qatlamlarga: domain (entity + port — core), application (use-case), infrastructure (DB adapter), presentation (controller). NestJS DI 8.2-bob — port'ni adapter'ga bog'laydi (provide/useClass — DIP). "Domain — pure services, ports — interfaces, adapters — providers." Bu — Clean'ning NestJS amaliyoti.

2.13. Qachon qaysi (pragmatik balans)

text
  ❗ HAR LOYIHAGA TO'LIQ CLEAN KERAK EMAS (over-engineering):

  Kichik CRUD / prototip:    oddiy layered (controller/service/repo — 8.1) yetadi
  O'rta loyiha:              layered + repository interfeys (DIP — 2.3)
  Katta/uzoq/murakkab biznes: Clean/Hexagonal (to'liq qatlamlar)
  Murakkab domen:            + DDD 9.4-bob

  Qoida: murakkablik biznes murakkabligiga mos bo'lsin (YAGNI — 9.1: 2.12)

Pragmatik balans: har loyihaga to'liq Clean kerak emas (over-engineering — 9.1: 2.12). Kichik CRUD — oddiy layered 8.1-bob yetadi; o'rta — layered + repo interfeys; katta/murakkab biznes — Clean/Hexagonal. Murakkablik biznes murakkabligiga mos bo'lsin. Clean'ning foydasi katta, uzoq yashaydigan loyihada (kichikda ortiqcha boilerplate). To'g'ri tanlov — senior mahorati.

2.13b. Clean Architecture va DDD aloqasi (9.4 ga ko'prik)

text
  Clean Architecture — SKELET (qatlamlar, bog'liqlik yo'nalishi)
  DDD               — MAZMUN (domen qatlamini nima to'ldiradi)

  Clean qatlam        │  DDD to'ldiruvchisi 9.4-bob
  ────────────────────┼──────────────────────────────────
  Entities            │  Entity, Value Object, Aggregate
  (domen model)       │  (ubiquitous language bilan nomlangan)
  Use Cases           │  Application Service, Domain Event
  Interface Adapters  │  Repository (impl), Anti-Corruption Layer
  Frameworks/Drivers  │  Infrastructure (DB, message bus)

  Modul chegarasi 2.12-bob ≈ DDD Bounded Context (9.4)

Clean va DDD — bir-birini to'ldiradi, raqib emas. Clean Architecture strukturani beradi: qaysi qatlam bor, bog'liqlik qayerga yo'naladi (Dependency Rule). Lekin domen qatlamini nima bilan to'ldirish — Clean aytmaydi. Aynan shu yerda DDD (Domain-Driven Design — 9.4) keladi: entity'ni value object va aggregate'larga bo'lish, biznes qoidalarini ubiquitous language (soha tili) bilan nomlash, domain event orqali aloqa. Amalda: Clean qatlamlarini quriladi (skelet), domen qatlami DDD naqshlari bilan to'ldiriladi (mazmun). Har Clean moduli/core'i 2.12-bob ko'pincha bitta DDD bounded contextiga (chegaralangan kontekst) to'g'ri keladi. Shuning uchun 9.4-bob bu bobning tabiiy davomi: murakkab domen uchun Clean skeleti majburiy shart, lekin yetarli emas — DDD mazmuni kerak.

2.14. Foyda va narx (trade-off)

text
  FOYDA:
  ✓ Texnologiyadan mustaqil (DB/framework almashtiriladigan)
  ✓ Test oson (biznes mantiq izolyatsiya — DB'siz — 8.11)
  ✓ Biznes mantiq aniq (entity/use-case'da)
  ✓ Uzoq yashash (o'zgarishga chidamli)

  NARX:
  ✗ Ko'proq kod (qatlamlar, interfeys, mapping)
  ✗ Boilerplate (domen ↔ ORM ↔ DTO o'tkazish)
  ✗ O'rganish egri chizig'i (jamoa tushunishi kerak)

Trade-off: foyda — mustaqillik, test, aniqlik, uzoq yashash; narx — ko'proq kod (qatlam/interfeys/mapping boilerplate), o'rganish. Kichik loyihada narx > foyda (oddiy yetadi); katta/murakkab biznesda foyda > narx (uzoq yashaydi). Tanlov — kontekstga bog'liq (dogma emas). Senior — qachon kerakligini biladi.

2.15. Best practices (arxitektura)

text
  ✓ Biznes mantiq markazda, texnologiya chekkada (2.4, 2.6)
  ✓ Dependency Rule (bog'liqlik ichkariga — 2.7)
  ✓ Entity — sof biznes (DB/framework'siz — 2.8)
  ✓ Use Case — bir stsenariy, port'larga tayanadi 2.9-bob
  ✓ Port (interfeys) + Adapter (impl — almashtiriladigan — 2.5)
  ✓ Repository interfeys (DB izolyatsiya — DIP — 9.2: 2.13)
  ✓ NestJS modul → qatlamlar (DI port→adapter — 2.12)
  ✓ Pragmatik (biznes murakkabligiga mos — YAGNI — 2.13)

3. Sintaksis — tez ma'lumotnoma

text
QATLAMLAR (ichkidan tashqiga):
Entity (biznes qoida) → Use Case (oqim) → Adapter (controller/repo) → Framework (DB)

DEPENDENCY RULE: bog'liqlik FAQAT ichkariga 2.7-bob

NestJS tuzilma 2.12-bob:
domain/ (entity + port interfeys) → application/ (use-case)
→ infrastructure/ (DB adapter) + presentation/ (controller)

DI: { provide: PortInterface, useClass: ConcreteAdapter }   // DIP (8.2)

4. Batafsil kod namunalari

Misol 1 — Domain layer (entity + port — 2.8, 2.5)

typescript
// domain/order.entity.ts — SOF biznes (DB/framework yo'q)
export class Order {
  private constructor(
    public readonly id: string,
    private readonly userId: string,
    private items: OrderItem[],
    private status: OrderStatus = "yangi",
  ) {}

  static yarat(userId: string, items: OrderItem[]): Order {
    if (items.length === 0) throw new Error("Buyurtma bo'sh bo'lmaydi");   // biznes qoida
    return new Order(crypto.randomUUID(), userId, items);
  }

  jamiSumma(): number {
    return this.items.reduce((s, i) => s + i.narx * i.miqdor, 0);
  }
  bekorQil(): void {
    if (this.status === "yetkazildi") throw new Error("Yetkazilgan bekor qilinmaydi");
    this.status = "bekor";
  }
  get holat() { return this.status; }
}

// domain/order.repository.ts — PORT (interfeys — core belgilaydi)
export interface OrderRepository {
  saqla(order: Order): Promise<void>;
  topById(id: string): Promise<Order | null>;
  topByUser(userId: string): Promise<Order[]>;
}

// domain/payment.gateway.ts — PORT
export interface PaymentGateway {
  tolov(summa: number, userId: string): Promise<boolean>;
}

Misol 2 — Application layer (use-case — 2.9)

typescript
// application/create-order.use-case.ts
import { Injectable, Inject } from "@nestjs/common";

@Injectable()
export class CreateOrderUseCase {
  constructor(
    @Inject("OrderRepository") private orderRepo: OrderRepository,    // PORT (2.5)
    @Inject("PaymentGateway") private payment: PaymentGateway,        // PORT
    @Inject("EventBus") private eventBus: EventBus,                   // PORT
  ) {}

  async bajar(input: { userId: string; items: OrderItem[] }): Promise<Order> {
    const order = Order.yarat(input.userId, input.items);            // entity (biznes qoida)

    const tolovOk = await this.payment.tolov(order.jamiSumma(), input.userId);
    if (!tolovOk) throw new Error("To'lov amalga oshmadi");

    await this.orderRepo.saqla(order);                               // port (DB detalini bilmaydi)
    await this.eventBus.elon("buyurtma_yaratildi", { orderId: order.id });
    return order;
  }
}
// ❗ Bu use-case TypeORM/NestJS HTTP'ni BILMAYDI (faqat port'lar)

Misol 3 — Infrastructure adapter (repository — 2.10)

typescript
// infrastructure/typeorm-order.repository.ts — driven adapter
@Injectable()
export class TypeOrmOrderRepository implements OrderRepository {     // PORT impl
  constructor(
    @InjectRepository(OrderOrmEntity) private repo: Repository<OrderOrmEntity>,   // (8.3)
  ) {}

  async saqla(order: Order): Promise<void> {
    const ormEntity = this.domainToOrm(order);                      // domen → ORM (mapping)
    await this.repo.save(ormEntity);
  }
  async topById(id: string): Promise<Order | null> {
    const row = await this.repo.findOneBy({ id });
    return row ? this.ormToDomain(row) : null;                      // ORM → domen
  }
  async topByUser(userId: string): Promise<Order[]> {
    const rows = await this.repo.findBy({ userId });
    return rows.map((r) => this.ormToDomain(r));
  }

  private domainToOrm(order: Order): OrderOrmEntity { /* mapping */ return {} as any; }
  private ormToDomain(row: OrderOrmEntity): Order { /* mapping */ return {} as any; }
}
// → TypeORM'ni Prisma'ga almashtirsa: yangi PrismaOrderRepository (use-case tegmaydi!)

Misol 4 — Presentation adapter (controller — 2.10)

typescript
// presentation/order.controller.ts — driving adapter
@Controller("orders")
export class OrderController {
  constructor(private createOrder: CreateOrderUseCase) {}           // use-case (2.9)

  @Post()
  @UseGuards(JwtAuthGuard)                                          // (8.9)
  async yarat(@Body() dto: CreateOrderDto, @CurrentUser() user: any) {
    const order = await this.createOrder.bajar({                    // use-case'ni chaqiradi
      userId: user.id,
      items: dto.items,
    });
    return { id: order.id, summa: order.jamiSumma(), holat: order.holat };   // domen → HTTP
  }
}
// → REST'ni GraphQL'ga almashtirsa: yangi resolver (use-case tegmaydi!)

Misol 5 — Module (DI port→adapter — 2.12)

typescript
// orders.module.ts — qatlamlarni bog'lash (DIP — 8.2)
@Module({
  imports: [TypeOrmModule.forFeature([OrderOrmEntity])],
  controllers: [OrderController],                                   // presentation
  providers: [
    CreateOrderUseCase,                                             // application
    GetOrdersUseCase,
    // PORT → ADAPTER bog'lash (DIP — almashtiriladigan)
    { provide: "OrderRepository", useClass: TypeOrmOrderRepository },   // domain port → infra
    { provide: "PaymentGateway", useClass: ClickPaymentAdapter },
    { provide: "EventBus", useClass: NestEventBus },
  ],
})
export class OrdersModule {}
// → DB almashtirsa: useClass: PrismaOrderRepository (boshqa hech narsa o'zgarmaydi)

Misol 6 — To'liq tuzilma (Hexagonal — 2.12)

text
  src/orders/
  ├── domain/                          ← CORE (hech narsaga bog'liq emas)
  │   ├── order.entity.ts                  (biznes qoida — Misol 1)
  │   ├── order-item.value-object.ts       (9.4 — value object)
  │   ├── order.repository.ts              (PORT interfeys — Misol 1)
  │   └── payment.gateway.ts               (PORT interfeys)
  ├── application/                     ← USE CASES
  │   ├── create-order.use-case.ts         (Misol 2)
  │   └── cancel-order.use-case.ts
  ├── infrastructure/                  ← DRIVEN ADAPTERS
  │   ├── typeorm-order.repository.ts      (Misol 3)
  │   ├── order.orm-entity.ts              (ORM — DB detali)
  │   └── click-payment.adapter.ts
  ├── presentation/                    ← DRIVING ADAPTERS
  │   ├── order.controller.ts              (Misol 4)
  │   └── dto/
  └── orders.module.ts                 (DI — Misol 5)

Misol 7 — Layered (oddiy — kichik loyiha — 2.2, 2.13)

typescript
// Kichik loyiha — oddiy layered (Clean ortiqcha)
@Controller("products")
class ProductController {                                           // presentation
  constructor(private service: ProductService) {}
  @Get() hammasi() { return this.service.hammasi(); }
}

@Injectable()
class ProductService {                                             // business
  constructor(@InjectRepository(Product) private repo: Repository<Product>) {}   // (8.3)
  hammasi() { return this.repo.find(); }                          // to'g'ridan repo
}
// → Kichik CRUD uchun bu YETADI (Clean over-engineering bo'lardi — 2.13)

Misol 8 — Test (Clean foydasi — 2.14)

typescript
// Use-case test — DB'siz (port mock — 8.11)
describe("CreateOrderUseCase", () => {
  it("to'lov muvaffaqiyatsiz → xato", async () => {
    const mockRepo: OrderRepository = { saqla: jest.fn(), topById: jest.fn(), topByUser: jest.fn() };
    const mockPayment: PaymentGateway = { tolov: jest.fn().mockResolvedValue(false) };   // to'lov yo'q
    const mockBus: EventBus = { elon: jest.fn() } as any;

    const useCase = new CreateOrderUseCase(mockRepo, mockPayment, mockBus);
    await expect(useCase.bajar({ userId: "1", items: [{ narx: 100, miqdor: 1 } as any] }))
      .rejects.toThrow("To'lov amalga oshmadi");
    expect(mockRepo.saqla).not.toHaveBeenCalled();                // saqlanmadi
  });
});
// ❗ DB, NestJS, HTTP yo'q — sof biznes mantiq test (tez, oson — Clean foydasi)

Misol 9 — Mapping (domen ↔ ORM ↔ DTO — 2.14 narx)

typescript
// Clean'da uch xil model (chegaralar uchun)
// 1. Domain entity (biznes — Misol 1)
class Order { /* biznes qoida */ }

// 2. ORM entity (DB — infrastructure)
@Entity("orders")
class OrderOrmEntity {
  @PrimaryColumn() id: string;
  @Column() userId: string;
  @Column("jsonb") items: any;
  @Column() status: string;
}

// 3. DTO (HTTP — presentation — 8.5)
class CreateOrderDto {
  @IsArray() items: OrderItemDto[];
}
class OrderResponseDto {
  id: string; summa: number; holat: string;
}
// ❗ Mapping kerak (domen↔ORM↔DTO) — bu Clean'ning narxi (boilerplate — 2.14)
// → kichik loyihada bir model yetadi (over-engineering'dan saqlaning)

Misol 10 — Arxitektura tanlovi (qaror daraxti — 2.13)

text
  Qaysi arxitektura? (qaror daraxti):

  Loyiha kichik (CRUD, prototip, MVP)?
  └─ HA → Oddiy Layered (controller/service/repo — 8.1, Misol 7)

  O'rta (biznes mantiq bor, DB almashishi mumkin)?
  └─ HA → Layered + Repository interfeys (DIP — 2.3)

  Katta (murakkab biznes, uzoq yashaydi, jamoa)?
  └─ HA → Clean/Hexagonal (to'liq qatlamlar — Misol 6)

  Domen juda murakkab (ko'p qoida, kontekst)?
  └─ HA → Clean + DDD 9.4-bob

  ❗ Shubhada — oddiyroq tanla (keyin refactor oson; YAGNI — 9.1: 2.12)

5. To'g'ri va noto'g'ri holatlar

1) Biznes mantiq controller'da

text
❌ controller'da biznes qoida + DB (aralash — 2.1)
✅ entity (qoida) + use-case (oqim) + adapter (2.6)

2) Use-case to'g'ridan DB'ga

text
❌ use-case'da TypeORM (bog'liq — 2.7)
✅ repository PORT (interfeys — 2.5)

3) Entity'da @Column/@Entity (ORM)

text
❌ domen entity = ORM entity (framework bog'liq — 2.8)
✅ domen entity (sof) + ORM entity (alohida — Misol 9)

4) Kichik loyihaga to'liq Clean

text
❌ CRUD'ga 4 qatlam + mapping (over-engineering — 2.13)
✅ oddiy layered (Misol 7)

5) Dependency Rule buzilishi

text
❌ entity use-case'ni import qiladi (tashqiga bog'liq — 2.7)
✅ bog'liqlik faqat ichkariga

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Over-engineering (kichik loyiha)

Sababi: har loyihaga Clean 2.13-bob. Yechimi: biznes murakkabligiga mos; oddiydan boshla.

Xato 2 — Domen = ORM entity

Sababi: mapping'dan qochish 2.8-bob. Yechimi: ajrat (sof domen + ORM); yoki kichik loyihada bir model.

Xato 3 — Use-case framework'ga bog'liq

Sababi: port o'rniga konkret 2.9-bob. Yechimi: interfeys (port); DI.

Xato 4 — Mapping boilerplate ko'p

Sababi: Clean narxi 2.14-bob. Yechimi: mapper klass/kutubxona; yoki oddiyroq arxitektura.

Xato 5 — Qatlam chegarasi buzilishi

Sababi: ichki tashqini import (Dependency Rule — 2.7). Yechimi: lint qoidasi (import cheklash); kod review.

Xato 6 — "Toza" lekin tushunarsiz

Sababi: haddan abstraksiya. Yechimi: balans (jamoa tushunadigan daraja).


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • SOLID 9.1-bob: DIP — Dependency Rule asosi.
  • Patterns 9.2-bob: Repository, Adapter, Facade; DTO — qatlamlararo ma'lumot (2.11b).
  • DDD 9.4-bob: Clean skeleti + DDD mazmuni (entity/VO/aggregate/bounded context — 2.13b); modul ≈ bounded context.
  • NestJS modul 8.1-bob: qatlamlar.
  • DI 8.2-bob: port → adapter.
  • Repository 8.3-bob: driven adapter.
  • Test 8.11-bob: use-case izolyatsiya.
  • Microservices 8.16-bob: har xizmat — Clean.

8. Eng yaxshi amaliyotlar (best practices)

  • Biznes mantiq markazda, texnologiya chekkada (2.4, 2.6).
  • Dependency Rule (bog'liqlik ichkariga — 2.7).
  • Entity — sof biznes (DB/framework'siz — 2.8).
  • Use Case — bir stsenariy, port'larga 2.9-bob.
  • Port (interfeys) + Adapter (impl) (almashtiriladigan — 2.5).
  • Repository interfeys (DB izolyatsiya — 9.2: 2.13).
  • NestJS modul → qatlamlar (DI port→adapter — 2.12).
  • Pragmatik (biznes murakkabligiga mos — YAGNI — 2.13).
  • Mapping (domen↔ORM↔DTO — kerak bo'lganda — 2.14, 2.11b).
  • Uch modelni ajrat (domain entity ≠ DTO ≠ ORM entity — 2.11b).
  • Screaming struktura (papka biznes sohasini aytadi, framework'ni emas — 2.11c).
  • Oddiydan boshla (refactor oson — Misol 10).

9. Amaliy loyiha: "Clean Architecture Buyurtma Tizimi"

Clean Architecture'ni amalda mustahkamlash.

Maqsad

Buyurtma tizimini Clean/Hexagonal arxitekturada qurish: domain, application, infrastructure, presentation qatlamlar.

Talablar (requirements)

  1. Domain: Order entity (biznes qoida) + port interfeyslar (Misol 1, 2.8).
  2. Application: CreateOrder/CancelOrder use-case (Misol 2, 2.9).
  3. Infrastructure: TypeORM repository (port impl + mapping — Misol 3, 2.10).
  4. Presentation: controller (use-case chaqiradi — Misol 4, 2.10).
  5. Module: DI port→adapter (Misol 5, 2.12).
  6. Dependency Rule: bog'liqlik ichkariga 2.7-bob.
  7. Mapping: domen↔ORM↔DTO (Misol 9, 2.14).
  8. Test: use-case DB'siz (port mock — Misol 8, 8.11).
  9. Almashtirish: TypeORM→Prisma (faqat adapter — 2.5).
  10. Balans: kerakli darajada (over-engineering yo'q — 2.13).

Maslahatlar (hint)

  • Entity sof (DB'siz — 2.8, 3-xato).
  • Use-case port'larga (2.9, 3-xato).
  • Dependency Rule (ichkariga — 2.7, 5-xato).
  • DI port→adapter 2.12-bob.
  • Test mock (8.11, Misol 8).
  • Balans (2.13, 1-xato).

"Tayyor" mezonlari (acceptance criteria)

  • Domain (entity + port).
  • Application (use-case).
  • Infrastructure (repository + mapping).
  • Presentation (controller).
  • Module (DI).
  • Dependency Rule.
  • Mapping.
  • Test (DB'siz).
  • Almashtirish (adapter).
  • Balans.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda ilova arxitektura uslublarini chuqur o'rgandik:

  • Nega arxitektura 2.1-bob; Layered (klassik qatlamli — 2.2, 2.3); Hexagonal (Ports & Adapters — core/port/adapter, driving/driven — 2.4, 2.5); Onion (konsentrik halqalar — 2.6b).
  • Clean Architecture (Entity/Use Case/Adapter/Framework — 2.6); Dependency Rule (bog'liqlik ichkariga — eng muhim — 2.7); input/output port va interactor (2.9b).
  • Entity (sof biznes — 2.8); Use Case (oqim — 2.9); Adapter (controller/repo — 2.10); Frameworks (detal — 2.11); entity≠DTO≠ORM (2.11b); Screaming Architecture (2.11c).
  • NestJS'da (modul → qatlamlar — 2.12); pragmatik balans (biznes murakkabligiga — 2.13, 2.14); Clean+DDD aloqasi (2.13b).

Keyingi bob — 9.4-bob: Domain-Driven Design (DDD) asoslari. Clean Architecture'ni bildik; endi murakkab domen (biznes soha)ni modellashtirish yondashuvini — DDD — o'rganamiz: entity, value object, aggregate, repository, bounded context, ubiquitous language, domain events. DDD — Clean Architecture'ning domen qatlamini chuqurlashtiradi (murakkab biznes uchun).


Foydalanilgan rasmiy/ishonchli manbalar

  • Robert C. Martin — "Clean Architecture: A Craftsman's Guide to Software Structure and Design" (kitob — Entities/Use Cases/Interface Adapters/Frameworks qatlamlari, Dependency Rule, "The Clean Architecture" blog posti)
  • Robert C. Martin — "Screaming Architecture" (blog — struktura biznes sohasini aks ettirishi haqida)
  • Alistair Cockburn — "Hexagonal Architecture (Ports & Adapters)" (asl maqola — port/adapter, driving/driven)
  • Jeffrey Palermo — "The Onion Architecture" (blog seriyasi — konsentrik halqalar, interfeyslar markazda)
  • Eric Evans — "Domain-Driven Design: Tackling Complexity in the Heart of Software" (domen qatlami mazmuni — 9.4 uchun)
  • Martin Fowler — "Patterns of Enterprise Application Architecture" (Layered/N-tier, Repository, DTO naqshlari)
  • NestJS rasmiy hujjatlari — Modules, Providers, Custom Providers (provide/useClass), Dependency Injection
Ulashish:Telegram'da

O'qiganingizni mustahkamlang

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!