WisarWisar
Dasturlash kitobi/9-QISM — Arxitektura27 daqiqa

9.3-bob: Clean Architecture, Layered, Hexagonal

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 — tashqicore use-case; driven — coretashqi repository). Adapter — implementatsiya (driving — controller/bot; driven — TypeORM/email). Core port'larni belgilaydi (men shunga muhtojman), adapter ularni amalga oshiradi. Almashtirish: TypeORMPrisma 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 — HTTPuse-case — 8.1), repository (driven — port implementatsiyasiDB — 8.3). Bu yerda format o'zgaradi (HTTP DTO domen, domen ORM entity). Adapter almashtiriladigan (RESTGraphQL, TypeORMPrisma). 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 (PostgreSQLMongo, NestJSExpress). 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 portadapter — 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 portadapter — 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 (domenORMDTO) — bu Clean'ning narxi (boilerplate — 2.14)
//  kichik loyihada bir model yetadi (over-engineering'dan qoch)

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)

   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 portadapter — 2.12).
  • Pragmatik (biznes murakkabligiga mos — YAGNI — 2.13).
  • Mapping (domenORMDTO — 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 portadapter (Misol 5, 2.12).
  6. Dependency Rule: bog'liqlik ichkariga 2.7-bob.
  7. Mapping: domenORMDTO (Misol 9, 2.14).
  8. Test: use-case DB'siz (port mock — Misol 8, 8.11).
  9. Almashtirish: TypeORMPrisma (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 portadapter 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

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
9.3-bob: Clean Architecture, Layered, Hexagonal — Wisar