10-bo‘lim

Ma'lumotlarni loyihalash

Domen modeli, ma'lumotlar bazasi sxemasi, migratsiyalar, kesh va ma'lumot yaxlitligi.

🕑 16 daqiqa o‘qish 📄 868 so‘z 👁 10 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Domen modeli
  2. Qiymat obyektlari
  3. Baza sxemasini loyihalash
  4. Loyihalash qoidalari
  5. Yumshoq o'chirish
  6. Migratsiyalar
  7. Xavfsiz migratsiya
  8. Ma'lumot yaxlitligi
  9. Tranzaksiyalar
  10. Keshlash
  11. Ko'p ishlatiladigan so'rovlarni tekshirish
  12. Ma'lumot arxivlash
  13. Xulosa

Ma'lumot - tizimning eng uzoq yashaydigan qismi. Kod bir necha marta qayta yoziladi, ma'lumot esa qoladi.

Domen modeli #

Kod ichida biznes tushunchalarini aks ettiruvchi obyektlar.

PHP
// YOMON: anemik model - faqat ma'lumot, mantiq yo'q
class Buyurtma
{
    public int $id;
    public int $mijozId;
    public string $holat;
    public float $jami;
    public array $elementlar;
}

// Mantiq boshqa joyda sochilgan
class BuyurtmaServisi
{
    public function bekorQiling(Buyurtma $buyurtma): void
    {
        if ($buyurtma->holat === 'yolda' || $buyurtma->holat === 'yetkazildi') {
            throw new Exception('Bekor qilib bo\'lmaydi');
        }

        $buyurtma->holat = 'bekor';
    }
}
PHP
// YAXSHI: boy model - ma'lumot va mantiq birga
final class Buyurtma
{
    private array $elementlar = [];

    private function __construct(
        public readonly BuyurtmaId $id,
        public readonly MijozId $mijozId,
        private BuyurtmaHolati $holat,
        public readonly DateTimeImmutable $yaratilgan,
    ) {}

    public static function yangi(MijozId $mijozId): self
    {
        return new self(
            BuyurtmaId::yaratish(),
            $mijozId,
            BuyurtmaHolati::Yangi,
            new DateTimeImmutable(),
        );
    }

    public function elementQoshing(Mahsulot $mahsulot, int $soni): void
    {
        if ($this->holat !== BuyurtmaHolati::Yangi) {
            throw new BuyurtmaOzgartirilmaydi($this->id);
        }

        if ($soni < 1) {
            throw new NotogriSoni($soni);
        }

        $this->elementlar[] = new BuyurtmaElementi(
            $mahsulot->id,
            $mahsulot->nomi,
            $mahsulot->narx,     // narx muzlatiladi
            $soni,
        );
    }

    public function jami(): Pul
    {
        return array_reduce(
            $this->elementlar,
            fn (Pul $yigindi, BuyurtmaElementi $e) => $yigindi->qoshing($e->summa()),
            Pul::nol(),
        );
    }

    public function bekorQiling(string $sabab): void
    {
        if (!$this->holat->bekorQilishMumkinmi()) {
            throw new BuyurtmaBekorQilinmaydi($this->id, $this->holat);
        }

        $this->holat = BuyurtmaHolati::Bekor;
    }
}
Boy model nima uchun yaxshiroq?
  1. Biznes qoidalari bir joyda - obyekt ichida
  2. Obyekt noto'g'ri holatga tusha olmaydi
  3. Kod biznes tilida o'qiladi
  4. Testlash oson - baza kerak emas

Anemik modelda esa qoida uch xil servisda takrorlanadi va biri unutiladi.

Qiymat obyektlari #

PHP
final class Pul
{
    private function __construct(
        public readonly int $tiyin,
        public readonly string $valyuta,
    ) {
        if ($tiyin < 0) {
            throw new InvalidArgumentException('Pul manfiy bo\'lolmaydi');
        }
    }

    public static function somdan(float $som, string $valyuta = 'UZS'): self
    {
        return new self((int) round($som * 100), $valyuta);
    }

    public static function nol(string $valyuta = 'UZS'): self
    {
        return new self(0, $valyuta);
    }

    public function qoshing(self $boshqa): self
    {
        $this->valyutaTekshiring($boshqa);

        return new self($this->tiyin + $boshqa->tiyin, $this->valyuta);
    }

    public function kopaytiring(int $son): self
    {
        return new self($this->tiyin * $son, $this->valyuta);
    }

    public function kattaroqmi(self $boshqa): bool
    {
        $this->valyutaTekshiring($boshqa);

        return $this->tiyin > $boshqa->tiyin;
    }

    public function som(): float
    {
        return $this->tiyin / 100;
    }

    private function valyutaTekshiring(self $boshqa): void
    {
        if ($this->valyuta !== $boshqa->valyuta) {
            throw new ValyutalarMosKelmadi($this->valyuta, $boshqa->valyuta);
        }
    }
}
PHP
$narx = Pul::somdan(250_000);
$jami = $narx->kopaytiring(3);

echo $jami->som();
Natija
750000
Qiymat obyektining uchta xususiyati
  1. O'zgarmas (immutable) - yaratilgandan keyin o'zgarmaydi
  2. Qiymat bo'yicha tenglik - id yo'q, mazmun muhim
  3. O'z-o'zini tekshiradi - noto'g'ri holatda yaratilmaydi

Misollar: Pul, Email, Telefon, Manzil, Sana oralig'i, Koordinata.

float $narx o'rniga Pul $narx ishlatish valyuta aralashib ketishining oldini oladi.

Baza sxemasini loyihalash #

Onlayn do'kon sxemasi mijozlar id (PK) email (UK) parol_xesh yaratilgan buyurtmalar id (PK) mijoz_id (FK) holat, jami yaratilgan elementlar id (PK) buyurtma_id (FK) mahsulot_id (FK) narx, soni mahsulotlar id (PK), nomi narx, zaxira kategoriya_id (FK) 1 N 1 N 1 N elementlar da narx nusxasi saqlanadi - ataylab qilingan denormalizatsiya
Har bir bog'lanish uchun aniq sabab bo'lishi kerak
SQL
CREATE TABLE buyurtmalar (
    id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    raqam       VARCHAR(20) NOT NULL,
    mijoz_id    BIGINT UNSIGNED NOT NULL,
    holat       ENUM('yangi','tolangan','yigilmoqda','yolda','yetkazildi','bekor')
                NOT NULL DEFAULT 'yangi',
    jami_tiyin  BIGINT UNSIGNED NOT NULL DEFAULT 0,
    valyuta     CHAR(3) NOT NULL DEFAULT 'UZS',
    yaratilgan  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    yangilangan TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
                ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uq_raqam (raqam),
    KEY idx_mijoz_sana (mijoz_id, yaratilgan DESC),
    KEY idx_holat (holat),

    CONSTRAINT fk_buyurtma_mijoz
        FOREIGN KEY (mijoz_id) REFERENCES mijozlar(id)
        ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Pulni butun sonda saqlang

jami_tiyin BIGINT - kasr emas.

DECIMAL ham to'g'ri, lekin butun son bilan hisoblash xatosi umuman bo'lmaydi va tillar orasida bir xil ishlaydi.

FLOAT esa hech qachon pul uchun ishlatilmasin.

Loyihalash qoidalari #

Sxema loyihalash tekshiruv ro'yxati
QoidaSabab
Har bir jadvalda birlamchi kalitQatorni aniq identifikatsiya qilish
Tashqi kalitlar e'lon qilinganYaxlitlikni baza kafolatlaydi
NOT NULL standartNULL alohida ma'no bildirsagina
Nomlar izchil (snake_case)O'qish va yozish oson
Jadval nomi ko'plikdamijozlar, buyurtmalar
yaratilgan va yangilanganDeyarli har doim kerak bo'ladi
Pul - butun son yoki DECIMALYaxlitlash xatosi bo'lmasin
utf8mb4 kodlashEmoji va barcha belgilar
Indekslar so'rovlarga mosTez ishlash
Nima uchun RESTRICT, CASCADE emas?
SQL
ON DELETE CASCADE    -- mijoz o'chsa, buyurtmalari ham o'chadi
ON DELETE RESTRICT   -- buyurtmasi bor mijozni o'chirib bo'lmaydi

Moliyaviy ma'lumot hech qachon avtomatik o'chirilmasligi kerak.

Mijoz "o'chirilsa" - u aslida arxivlanadi:

SQL
ALTER TABLE mijozlar ADD ochirilgan TIMESTAMP NULL;

Yumshoq o'chirish #

PHP
trait YumshoqOchirish
{
    public function ochiring(): void
    {
        $this->ochirilgan = new DateTimeImmutable();
    }

    public function ochirilganmi(): bool
    {
        return $this->ochirilgan !== null;
    }
}
SQL
-- Faol yozuvlar uchun ko'rinish
CREATE VIEW faol_mijozlar AS
SELECT * FROM mijozlar WHERE ochirilgan IS NULL;
Yumshoq o'chirishning kamchiliklari
  1. Har bir so'rovga WHERE ochirilgan IS NULL qo'shish kerak - biri unutiladi
  2. UNIQUE cheklovlar buziladi: o'chirilgan email qayta ishlatilmaydi
  3. Jadval kattalashib boradi

Yechim: o'chirilgan yozuvlarni arxiv jadvaliga ko'chirish.

Migratsiyalar #

PHP
// database/migrations/2026_08_15_120000_buyurtmalarga_izoh_qoshish.php
final class BuyurtmalargaIzohQoshish
{
    public function yuqoriga(PDO $pdo): void
    {
        $pdo->exec("
            ALTER TABLE buyurtmalar
            ADD COLUMN izoh VARCHAR(500) NULL AFTER holat
        ");
    }

    public function pastga(PDO $pdo): void
    {
        $pdo->exec("ALTER TABLE buyurtmalar DROP COLUMN izoh");
    }
}
Migratsiyani ishga tushgandan keyin o'zgartirmang

Migratsiya serverga chiqarilgan bo'lsa, u o'zgarmas hisoblanadi.

Xato bo'lsa - yangi migratsiya yozing:

PHP
// 2026_08_16_090000_izoh_uzunligini_oshirish.php
$pdo->exec("ALTER TABLE buyurtmalar MODIFY izoh TEXT NULL");

Xavfsiz migratsiya #

PHP
// XAVFLI: to'xtatishsiz deploy da ustun yo'qolsa - eski kod buziladi
$pdo->exec("ALTER TABLE mijozlar DROP COLUMN eski_telefon");
Kengaytir va qisqartir naqshi 1. Kengaytirish Yangi ustun qo'shish (eski qoladi) 2. Ikkiga yozish Kod ikkala ustunga yozadi 3. Ko'chirish Eski ma'lumotni yangisiga 4. Qisqartirish Eski ustunni o'chirish Har bir qadam alohida deploy Har qanday paytda eski va yangi kod birga ishlay oladi
To'xtatishsiz deploy uchun majburiy yondashuv

Ma'lumot yaxlitligi #

Uch darajada himoya:

PHP
// 1. Ilova darajasi - foydalanuvchiga tushunarli xabar
if ($soni < 1) {
    throw new NotogriSoni('Soni kamida 1 bo\'lishi kerak');
}
SQL
-- 2. Baza darajasi - oxirgi himoya
CONSTRAINT chk_soni CHECK (soni >= 1)
PHP
// 3. Domen darajasi - obyekt noto'g'ri holatga tushmaydi
final class Soni
{
    public function __construct(public readonly int $qiymat)
    {
        if ($qiymat < 1) {
            throw new InvalidArgumentException('Soni musbat bo\'lishi kerak');
        }
    }
}
Nima uchun uch marta?

Ilova kodi o'zgaradi, birov INSERT ni qo'lda bajaradi, migratsiya skripti xato yozadi.

Baza - oxirgi himoya chizig'i. U hech qachon ishonmasin.

Tranzaksiyalar #

PHP
final class BuyurtmaServisi
{
    public function yarating(MijozId $mijozId, Savat $savat): Buyurtma
    {
        $this->pdo->beginTransaction();

        try {
            $buyurtma = Buyurtma::yangi($mijozId);

            foreach ($savat->elementlar() as $element) {
                $mahsulot = $this->mahsulotlar->qulflabToping($element->mahsulotId);

                if (!$mahsulot->zaxiraYetarlimi($element->soni)) {
                    throw new ZaxiraYetarliEmas($mahsulot->nomi);
                }

                $buyurtma->elementQoshing($mahsulot, $element->soni);
                $this->mahsulotlar->zaxiraKamaytiring($mahsulot->id, $element->soni);
            }

            $this->buyurtmalar->saqlang($buyurtma);
            $this->pdo->commit();

            return $buyurtma;
        } catch (Throwable $xato) {
            $this->pdo->rollBack();
            throw $xato;
        }
    }
}
Tranzaksiya ichida tashqi chaqiruv qilmang
PHP
$this->pdo->beginTransaction();

$this->buyurtmalar->saqlang($buyurtma);
$this->tolovAPI->tolang($summa);       // XAVFLI: 30 soniya kutishi mumkin
$this->email->yuboring($xabar);        // XAVFLI

$this->pdo->commit();

Tashqi xizmat sekin javob bersa - baza qulflari uzoq turadi va butun tizim sekinlashadi.

To'g'ri yondashuv: tranzaksiyani yoping, keyin tashqi chaqiruvni qiling (yoki navbatga qo'ying).

Keshlash #

Kesh qatlamlari va tezlik Brauzer 0 ms CDN 10-30 ms Redis 1-5 ms Ilova keshi 0.1 ms MySQL 10-200 ms Keshning ikkita qiyin muammosi 1. Keshni qachon tozalash kerak? (eskirgan ma'lumot xavfi) 2. Kesh kalitini qanday nomlash kerak? (to'qnashuv xavfi)
Har bir qatlam tezroq, lekin eskirgan ma'lumot xavfi bor
PHP
final class KeshlanganMahsulotRepozitoriyi implements MahsulotRepozitoriyi
{
    public function __construct(
        private MahsulotRepozitoriyi $keyingi,
        private KeshInterfeysi $kesh,
        private int $muddat = 3600,
    ) {}

    public function toping(int $id): ?Mahsulot
    {
        $kalit = "mahsulot:{$id}";

        $saqlangan = $this->kesh->oling($kalit);
        if ($saqlangan !== null) {
            return $saqlangan;
        }

        $mahsulot = $this->keyingi->toping($id);

        if ($mahsulot !== null) {
            $this->kesh->saqlang($kalit, $mahsulot, $this->muddat);
        }

        return $mahsulot;
    }

    public function saqlang(Mahsulot $mahsulot): Mahsulot
    {
        $natija = $this->keyingi->saqlang($mahsulot);
        $this->kesh->ochiring("mahsulot:{$natija->id}");

        return $natija;
    }
}
Dekorator naqshi bilan kesh

Asosiy repozitoriy kesh haqida hech nima bilmaydi.

Keshni yoqish/o'chirish - shunchaki dekoratorni qo'shish yoki olib tashlash.

Keshni qachon ishlatmaslik kerak?
  • Ma'lumot tez-tez o'zgarsa
  • Eskirgan ma'lumot qabul qilib bo'lmaydigan bo'lsa (hisob qoldig'i)
  • Foydalanuvchiga xos ma'lumot (uni keshlash - xavfsizlik xavfi)
  • Sekinlik sababi kesh emas, yomon so'rov bo'lsa

Avval so'rovni optimallashtiring, keyin kesh qo'shing.

Ko'p ishlatiladigan so'rovlarni tekshirish #

PHP
// N+1 muammosi
$buyurtmalar = $repozitoriy->hammasi();      // 1 so'rov

foreach ($buyurtmalar as $buyurtma) {
    echo $buyurtma->mijoz()->ism;            // har biri uchun 1 so'rov!
}
// Jami: 1 + N so'rov
PHP
// Yechim: oldindan yuklash
$buyurtmalar = $repozitoriy->hammasiMijozlarBilan();   // 2 so'rov
SQL
SELECT b.*, m.ism, m.email
FROM buyurtmalar b
JOIN mijozlar m ON m.id = b.mijoz_id
WHERE b.yaratilgan > ?
N+1 - eng ko'p uchraydigan ishlash muammosi

100 ta buyurtma sahifasi 101 ta so'rov yuborsa, sahifa 2 soniyada ochiladi.

Har bir sahifada nechta so'rov ketayotganini o'lchang. Ishlab chiqish muhitida so'rovlar sonini ko'rsatuvchi panel o'rnating.

Ma'lumot arxivlash #

SQL
-- Eski buyurtmalarni arxivga ko'chirish
CREATE TABLE buyurtmalar_arxiv LIKE buyurtmalar;

INSERT INTO buyurtmalar_arxiv
SELECT * FROM buyurtmalar
WHERE yaratilgan < DATE_SUB(NOW(), INTERVAL 2 YEAR);

DELETE FROM buyurtmalar
WHERE yaratilgan < DATE_SUB(NOW(), INTERVAL 2 YEAR);
Bo'lish (partitioning)

Juda katta jadvallar uchun MySQL o'zi bo'lishni qo'llab-quvvatlaydi:

SQL
ALTER TABLE buyurtmalar
PARTITION BY RANGE (YEAR(yaratilgan)) (
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p2025 VALUES LESS THAN (2026),
    PARTITION p2026 VALUES LESS THAN (2027),
    PARTITION pmax  VALUES LESS THAN MAXVALUE
);

Eski bo'limni o'chirish bir zumda bajariladi.

Amaliy topshiriq
  1. Anemik modelni boy modelga aylantiring.
  2. Pul qiymat obyektini yozing va testlang.
  3. Email va Telefon qiymat obyektlarini yozing.
  4. Onlayn do'kon uchun to'liq SQL sxemasini yozing.
  5. Barcha tashqi kalitlar va indekslarni qo'shing.
  6. Ikkita migratsiya yozing: ustun qo'shish va uni o'zgartirish.
  7. "Kengaytir va qisqartir" naqshi bo'yicha ustun nomini o'zgartiring.
  8. Kesh dekoratorini yozing.
  9. Loyihangizda N+1 muammosini toping va tuzating.
  10. Yumshoq o'chirish uchun ko'rinish (VIEW) yarating.

Xulosa #

  • Boy domen modeli biznes qoidalarini obyekt ichida saqlaydi.
  • Qiymat obyektlari o'zgarmas, o'z-o'zini tekshiradi va turlarni aralashtirmaydi.
  • Pulni butun son yoki DECIMAL da saqlang, hech qachon FLOAT da emas.
  • Moliyaviy ma'lumot uchun RESTRICT, CASCADE emas.
  • Migratsiya ishga tushgandan keyin o'zgarmas - yangisini yozing.
  • To'xtatishsiz deploy uchun kengaytir va qisqartir naqshi.
  • Yaxlitlikni uch darajada ta'minlang: domen, ilova, baza.
  • Tranzaksiya ichida tashqi chaqiruv qilmang.
  • Keshni dekorator sifatida qo'shing - asosiy kod bilmasin.
  • N+1 muammosi - eng ko'p uchraydigan ishlash muammosi.

Keyingi bo'limda API loyihalashni ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.