20-bo‘lim

Amaliy loyiha - kutubxona tizimi

Talablardan deploy gacha - barcha bilimlarni birlashtirgan to'liq muhandislik tsikli.

🕑 13 daqiqa o‘qish 📄 829 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Bosqich 1 - muammo va talablar
  2. Aniqlashtiruvchi savollar
  3. Talablar
  4. Foydalanuvchi hikoyalari
  5. Bosqich 2 - domen modeli
  6. Bosqich 3 - testlar (TDD)
  7. Bosqich 4 - arxitektura
  8. Bosqich 5 - baza sxemasi
  9. Bosqich 6 - hisobotlar
  10. Bosqich 7 - CI quvuri
  11. Bosqich 8 - deploy
  12. Bosqich 9 - hujjatlar
  13. Bosqich 10 - natijalar
  14. Retrospektiva
  15. Yakuniy tekshiruv ro'yxati
  16. Yakuniy xulosa

Yigirma bo'lim davomida o'rgangan hamma narsani bitta loyihada qo'llaymiz - g'oyadan ishlab chiqarishgacha.

Bosqich 1 - muammo va talablar #

Mijoz so'rovi

*"Bizda maktab kutubxonasi bor. Kitoblar daftarga yozib boriladi. Kim qaysi kitobni olganini topish uchun butun daftarni varaqlash kerak. Kechikkanlarni ham qo'lda hisoblaymiz. Buni kompyuterga o'tkazsak bo'ladimi?"*

Aniqlashtiruvchi savollar #

Natija
1. Nechta kitob va nechta o'quvchi bor?
   → ~3000 kitob, ~800 o'quvchi

2. Kuniga nechta ijara operatsiyasi?
   → 40-60 ta, dam olish kunlari kam

3. Kim ishlatadi?
   → 2 ta kutubxonachi, direktor (hisobotlar uchun)

4. O'quvchilar o'zi kirishi kerakmi?
   → Birinchi versiyada yo'q, keyinroq

5. Kitob qidiruvi qanday bo'ladi?
   → Nom, muallif, inventar raqami bo'yicha

6. Ijara muddati?
   → 14 kun, bir marta 7 kunga uzaytirish mumkin

7. Kechikkanda nima bo'ladi?
   → Ro'yxat chiqariladi, sinf rahbariga beriladi

8. Internet bormi?
   → Bor, lekin ba'zan uziladi

9. Byudjet va muddat?
   → Kichik, 6-8 hafta
Savollar javoblardan muhimroq

"Nechta kitob?" degan savol arxitekturani belgilaydi:

  • 3000 kitob → oddiy MySQL yetarli, murakkab qidiruv dvigateli kerak emas
  • 3 million kitob → butunlay boshqa yechim

Talablar #

MARKDOWN
## Funksional talablar

### Kitoblar
- FT-01 Kutubxonachi kitob qo'sha oladi (Must)
- FT-02 Kitobni tahrirlash va o'chirish (Must)
- FT-03 Nom, muallif, inventar raqami bo'yicha qidiruv (Must)
- FT-04 Kitob nusxalarini boshqarish (Must)

### O'quvchilar
- FT-05 O'quvchi qo'shish va tahrirlash (Must)
- FT-06 Sinf bo'yicha filtrlash (Should)
- FT-07 CSV dan ommaviy import (Could)

### Ijara
- FT-08 Kitobni ijaraga berish (Must)
- FT-09 Qaytarishni qayd etish (Must)
- FT-10 Muddatni uzaytirish (Must)
- FT-11 Ijara tarixini ko'rish (Should)

### Hisobotlar
- FT-12 Kechikkan kitoblar ro'yxati (Must)
- FT-13 Sinf bo'yicha hisobot (Should)
- FT-14 Ommabop kitoblar (Could)

## Nofunksional talablar

- NFT-01 Qidiruv 1 soniyadan tez (3000 kitobda)
- NFT-02 Bir vaqtda 5 foydalanuvchi
- NFT-03 Har kuni avtomatik zaxira nusxa
- NFT-04 Internet uzilganda ogohlantirish ko'rsatilsin
- NFT-05 Eski kompyuterlarda ishlasin (Chrome 100+)
- NFT-06 Parollar xeshlangan holda saqlansin

## Doiradan tashqarida
- O'quvchilar shaxsiy kabineti
- Elektron kitoblar
- Jarima hisoblash
- Boshqa kutubxonalar bilan integratsiya

Foydalanuvchi hikoyalari #

Natija
Men kutubxonachi sifatida
kitobni inventar raqami bo'yicha tez topishni xohlayman,
chunki o'quvchi navbatda kutib turadi.

Qabul mezonlari:
1. Qidiruv maydoniga raqam kiritilganda natija darhol ko'rinadi
2. Kitob mavjudligi yoki kimda ekani ko'rsatiladi
3. Topilmasa - tushunarli xabar chiqadi
4. Qisman moslik ham topiladi (masalan "1234" → "INV-001234")

Bosqich 2 - domen modeli #

PHP
// manba/Domen/Kitob.php
final class Kitob
{
    private array $nusxalar = [];

    private function __construct(
        public readonly KitobId $id,
        private string $nomi,
        private string $muallif,
        private ?int $nashrYili,
        private ?string $isbn,
    ) {}

    public static function yangi(
        string $nomi,
        string $muallif,
        ?int $nashrYili = null,
        ?string $isbn = null,
    ): self {
        if (trim($nomi) === '') {
            throw new InvalidArgumentException('Kitob nomi bo\'sh bo\'lolmaydi');
        }

        if ($nashrYili !== null && ($nashrYili < 1400 || $nashrYili > (int) date('Y'))) {
            throw new InvalidArgumentException('Noto\'g\'ri nashr yili');
        }

        return new self(KitobId::yaratish(), trim($nomi), trim($muallif), $nashrYili, $isbn);
    }

    public function nusxaQoshing(string $inventarRaqami): Nusxa
    {
        foreach ($this->nusxalar as $mavjud) {
            if ($mavjud->inventarRaqami === $inventarRaqami) {
                throw new InventarRaqamiTakrorlandi($inventarRaqami);
            }
        }

        $nusxa = Nusxa::yangi($this->id, $inventarRaqami);
        $this->nusxalar[] = $nusxa;

        return $nusxa;
    }

    public function mavjudNusxalar(): array
    {
        return array_values(array_filter(
            $this->nusxalar,
            fn (Nusxa $n) => $n->mavjudmi(),
        ));
    }
}
PHP
// manba/Domen/Ijara.php
final class Ijara
{
    private const MUDDAT_KUN = 14;
    private const UZAYTIRISH_KUN = 7;
    private const MAKS_UZAYTIRISH = 1;

    private function __construct(
        public readonly IjaraId $id,
        public readonly NusxaId $nusxaId,
        public readonly OquvchiId $oquvchiId,
        public readonly DateTimeImmutable $berilgan,
        private DateTimeImmutable $qaytarishMuddati,
        private ?DateTimeImmutable $qaytarilgan,
        private int $uzaytirishlar,
    ) {}

    public static function yangi(
        NusxaId $nusxaId,
        OquvchiId $oquvchiId,
        DateTimeImmutable $bugun,
    ): self {
        return new self(
            IjaraId::yaratish(),
            $nusxaId,
            $oquvchiId,
            $bugun,
            $bugun->modify('+' . self::MUDDAT_KUN . ' days'),
            null,
            0,
        );
    }

    public function uzaytiring(DateTimeImmutable $bugun): void
    {
        if ($this->qaytarilgan !== null) {
            throw new IjaraAllaqachonYopilgan($this->id);
        }

        if ($this->uzaytirishlar >= self::MAKS_UZAYTIRISH) {
            throw new UzaytirishChegarasi(self::MAKS_UZAYTIRISH);
        }

        if ($this->kechikkanmi($bugun)) {
            throw new KechikkanIjaraUzaytirilmaydi($this->kechikkanKunlar($bugun));
        }

        $this->qaytarishMuddati = $this->qaytarishMuddati
            ->modify('+' . self::UZAYTIRISH_KUN . ' days');
        $this->uzaytirishlar++;
    }

    public function qaytaring(DateTimeImmutable $bugun): void
    {
        if ($this->qaytarilgan !== null) {
            throw new IjaraAllaqachonYopilgan($this->id);
        }

        $this->qaytarilgan = $bugun;
    }

    public function kechikkanmi(DateTimeImmutable $bugun): bool
    {
        if ($this->qaytarilgan !== null) {
            return false;
        }

        return $bugun > $this->qaytarishMuddati;
    }

    public function kechikkanKunlar(DateTimeImmutable $bugun): int
    {
        if (!$this->kechikkanmi($bugun)) {
            return 0;
        }

        return (int) $this->qaytarishMuddati->diff($bugun)->days;
    }
}
DateTimeImmutable $bugun parametr sifatida

date() yoki new DateTime() ni domen ichida chaqirmang.

Vaqtni tashqaridan bering - shunda testda istalgan sanani o'rnatish mumkin:

PHP
$ijara->uzaytiring(new DateTimeImmutable('2026-09-01'));

Aks holda "kechikkan ijara" testini yozib bo'lmaydi.

Bosqich 3 - testlar (TDD) #

PHP
final class IjaraTest extends TestCase
{
    private DateTimeImmutable $bugun;

    protected function setUp(): void
    {
        $this->bugun = new DateTimeImmutable('2026-09-01');
    }

    public function test_yangi_ijara_muddati_14_kun(): void
    {
        $ijara = Ijara::yangi(NusxaId::yaratish(), OquvchiId::yaratish(), $this->bugun);

        $this->assertFalse($ijara->kechikkanmi($this->bugun->modify('+13 days')));
        $this->assertTrue($ijara->kechikkanmi($this->bugun->modify('+15 days')));
    }

    public function test_uzaytirish_muddatni_7_kunga_oshiradi(): void
    {
        $ijara = Ijara::yangi(NusxaId::yaratish(), OquvchiId::yaratish(), $this->bugun);

        $ijara->uzaytiring($this->bugun->modify('+10 days'));

        $this->assertFalse($ijara->kechikkanmi($this->bugun->modify('+20 days')));
        $this->assertTrue($ijara->kechikkanmi($this->bugun->modify('+22 days')));
    }

    public function test_ikki_marta_uzaytirib_bolmaydi(): void
    {
        $ijara = Ijara::yangi(NusxaId::yaratish(), OquvchiId::yaratish(), $this->bugun);
        $ijara->uzaytiring($this->bugun->modify('+10 days'));

        $this->expectException(UzaytirishChegarasi::class);

        $ijara->uzaytiring($this->bugun->modify('+18 days'));
    }

    public function test_kechikkan_ijarani_uzaytirib_bolmaydi(): void
    {
        $ijara = Ijara::yangi(NusxaId::yaratish(), OquvchiId::yaratish(), $this->bugun);

        $this->expectException(KechikkanIjaraUzaytirilmaydi::class);

        $ijara->uzaytiring($this->bugun->modify('+20 days'));
    }

    public function test_qaytarilgan_ijara_kechikkan_hisoblanmaydi(): void
    {
        $ijara = Ijara::yangi(NusxaId::yaratish(), OquvchiId::yaratish(), $this->bugun);
        $ijara->qaytaring($this->bugun->modify('+10 days'));

        $this->assertFalse($ijara->kechikkanmi($this->bugun->modify('+30 days')));
    }

    /**
     * @dataProvider kechikishHolatlari
     */
    public function test_kechikkan_kunlar_togri_hisoblanadi(
        string $bugungi,
        int $kutilgan,
    ): void {
        $ijara = Ijara::yangi(NusxaId::yaratish(), OquvchiId::yaratish(), $this->bugun);

        $this->assertEquals(
            $kutilgan,
            $ijara->kechikkanKunlar(new DateTimeImmutable($bugungi)),
        );
    }

    public static function kechikishHolatlari(): array
    {
        return [
            'muddat ichida'      => ['2026-09-10', 0],
            'aynan muddat kuni'  => ['2026-09-15', 0],
            'bir kun kechikdi'   => ['2026-09-16', 1],
            'bir hafta kechikdi' => ['2026-09-22', 7],
        ];
    }
}

Bosqich 4 - arxitektura #

Natija
manba/
├── Domen/
│   ├── Kitob.php
│   ├── Nusxa.php
│   ├── Oquvchi.php
│   ├── Ijara.php
│   ├── Identifikatorlar/
│   └── Istisnolar/
├── Ilova/
│   ├── KitobServisi.php
│   ├── IjaraServisi.php
│   ├── HisobotServisi.php
│   └── Sorovlar/
├── Infratuzilma/
│   ├── Repozitoriylar/
│   │   ├── MysqlKitobRepozitoriyi.php
│   │   └── MysqlIjaraRepozitoriyi.php
│   ├── Soat.php
│   └── Jurnal.php
└── Taqdimot/
    ├── Kontrollerlar/
    ├── Shablonlar/
    └── Middleware/
PHP
// Ilova qatlami - biznes stsenariylari
final class IjaraServisi
{
    public function __construct(
        private KitobRepozitoriyi $kitoblar,
        private IjaraRepozitoriyi $ijaralar,
        private OquvchiRepozitoriyi $oquvchilar,
        private SoatInterfeysi $soat,
        private LoggerInterface $jurnal,
    ) {}

    public function bering(IjaraBerishSorovi $sorov): Ijara
    {
        $oquvchi = $this->oquvchilar->toping($sorov->oquvchiId)
            ?? throw new OquvchiTopilmadi($sorov->oquvchiId);

        if (!$oquvchi->faolmi()) {
            throw new OquvchiFaolEmas($oquvchi->id);
        }

        $ochiqIjaralar = $this->ijaralar->oquvchiningOchiqlari($oquvchi->id);

        if (count($ochiqIjaralar) >= Oquvchi::MAKS_IJARA) {
            throw new IjaraChegarasi(Oquvchi::MAKS_IJARA);
        }

        foreach ($ochiqIjaralar as $ijara) {
            if ($ijara->kechikkanmi($this->soat->bugun())) {
                throw new KechikkanKitobBor($ijara->nusxaId);
            }
        }

        $nusxa = $this->kitoblar->nusxaToping($sorov->nusxaId)
            ?? throw new NusxaTopilmadi($sorov->nusxaId);

        if (!$nusxa->mavjudmi()) {
            throw new NusxaBandUnamed($nusxa->id);
        }

        $ijara = Ijara::yangi($nusxa->id, $oquvchi->id, $this->soat->bugun());

        $this->ijaralar->tranzaksiyada(function () use ($ijara, $nusxa) {
            $this->ijaralar->saqlang($ijara);
            $this->kitoblar->nusxaHolatiOzgartiring($nusxa->id, NusxaHolati::Ijarada);
        });

        $this->jurnal->info('ijara_berildi', [
            'ijara_id'   => (string) $ijara->id,
            'oquvchi_id' => (string) $oquvchi->id,
            'nusxa_id'   => (string) $nusxa->id,
        ]);

        return $ijara;
    }
}

Bosqich 5 - baza sxemasi #

SQL
CREATE TABLE kitoblar (
    id          CHAR(26) PRIMARY KEY,
    nomi        VARCHAR(300) NOT NULL,
    muallif     VARCHAR(200) NOT NULL,
    nashr_yili  SMALLINT UNSIGNED NULL,
    isbn        VARCHAR(20) NULL,
    yaratilgan  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    yangilangan TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
                ON UPDATE CURRENT_TIMESTAMP,

    KEY idx_muallif (muallif),
    FULLTEXT KEY ft_qidiruv (nomi, muallif)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE nusxalar (
    id               CHAR(26) PRIMARY KEY,
    kitob_id         CHAR(26) NOT NULL,
    inventar_raqami  VARCHAR(30) NOT NULL,
    holat            ENUM('mavjud','ijarada','yoqolgan','tamirda')
                     NOT NULL DEFAULT 'mavjud',
    qoshilgan        DATE NOT NULL,

    UNIQUE KEY uq_inventar (inventar_raqami),
    KEY idx_kitob_holat (kitob_id, holat),

    CONSTRAINT fk_nusxa_kitob
        FOREIGN KEY (kitob_id) REFERENCES kitoblar(id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE oquvchilar (
    id          CHAR(26) PRIMARY KEY,
    ism         VARCHAR(80) NOT NULL,
    familiya    VARCHAR(80) NOT NULL,
    sinf        VARCHAR(10) NOT NULL,
    tugilgan    DATE NULL,
    faol        BOOLEAN NOT NULL DEFAULT TRUE,
    yaratilgan  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

    KEY idx_sinf (sinf, familiya),
    FULLTEXT KEY ft_ism (ism, familiya)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE ijaralar (
    id                CHAR(26) PRIMARY KEY,
    nusxa_id          CHAR(26) NOT NULL,
    oquvchi_id        CHAR(26) NOT NULL,
    berilgan          DATE NOT NULL,
    qaytarish_muddati DATE NOT NULL,
    qaytarilgan       DATE NULL,
    uzaytirishlar     TINYINT UNSIGNED NOT NULL DEFAULT 0,
    bergan_xodim_id   CHAR(26) NOT NULL,

    KEY idx_ochiq (qaytarilgan, qaytarish_muddati),
    KEY idx_oquvchi (oquvchi_id, berilgan DESC),
    KEY idx_nusxa (nusxa_id, berilgan DESC),

    CONSTRAINT fk_ijara_nusxa
        FOREIGN KEY (nusxa_id) REFERENCES nusxalar(id) ON DELETE RESTRICT,
    CONSTRAINT fk_ijara_oquvchi
        FOREIGN KEY (oquvchi_id) REFERENCES oquvchilar(id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
idx_ochiq (qaytarilgan, qaytarish_muddati)

Eng ko'p ishlatiladigan so'rov - kechikkanlar ro'yxati:

SQL
SELECT * FROM ijaralar
WHERE qaytarilgan IS NULL AND qaytarish_muddati < CURDATE();

Indeks aynan shu so'rov uchun tuzilgan.

Bosqich 6 - hisobotlar #

PHP
final class HisobotServisi
{
    public function __construct(
        private PDO $pdo,
        private SoatInterfeysi $soat,
    ) {}

    /** @return KechikkanQator[] */
    public function kechikkanlar(): array
    {
        $stmt = $this->pdo->prepare("
            SELECT
                o.ism, o.familiya, o.sinf,
                k.nomi AS kitob, k.muallif,
                n.inventar_raqami,
                i.qaytarish_muddati,
                DATEDIFF(:bugun, i.qaytarish_muddati) AS kechikkan_kun
            FROM ijaralar i
            JOIN nusxalar   n ON n.id = i.nusxa_id
            JOIN kitoblar   k ON k.id = n.kitob_id
            JOIN oquvchilar o ON o.id = i.oquvchi_id
            WHERE i.qaytarilgan IS NULL
              AND i.qaytarish_muddati < :bugun2
            ORDER BY o.sinf, kechikkan_kun DESC
        ");

        $bugun = $this->soat->bugun()->format('Y-m-d');
        $stmt->execute(['bugun' => $bugun, 'bugun2' => $bugun]);

        return array_map(
            fn (array $q) => KechikkanQator::qatordan($q),
            $stmt->fetchAll(),
        );
    }
}
Takrorlanuvchi nomlangan parametr

PDO::ATTR_EMULATE_PREPARES => false bo'lganda bir nom ikki marta ishlatilishi mumkin emas - shuning uchun :bugun va :bugun2.

Bu xatoni topish qiyin: SQLSTATE[HY093]: Invalid parameter number.

Bosqich 7 - CI quvuri #

YAML
# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  sifat:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.3' }

      - uses: actions/cache@v4
        with:
          path: vendor
          key: composer-${{ hashFiles('composer.lock') }}

      - run: composer install --prefer-dist --no-progress
      - run: vendor/bin/php-cs-fixer fix --dry-run --diff
      - run: vendor/bin/phpstan analyse --level=8
      - run: composer audit

  testlar:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: sinov
          MYSQL_DATABASE: kutubxona_sinov
        ports: ['3306:3306']
        options: >-
          --health-cmd="mysqladmin ping" --health-interval=10s --health-retries=5

    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.3', coverage: xdebug }

      - run: composer install --prefer-dist --no-progress
      - run: php tools/migrate.php
        env: { DB_HOST: 127.0.0.1, DB_PAROL: sinov }
      - run: vendor/bin/phpunit --coverage-text

Bosqich 8 - deploy #

Terminal
#!/bin/bash
set -euo pipefail

RELIZ=$(date +%Y%m%d%H%M%S)
ILDIZ=/var/www/kutubxona
YANGI=$ILDIZ/relizlar/$RELIZ

echo "==> Zaxira nusxa"
mysqldump --single-transaction kutubxona | gzip > "$ILDIZ/zaxira/pre_${RELIZ}.sql.gz"

echo "==> Kodni olish"
mkdir -p "$YANGI"
git -C "$ILDIZ/manba" fetch --tags
git -C "$ILDIZ/manba" archive "${1:-main}" | tar -x -C "$YANGI"

echo "==> Bog'liqliklar"
cd "$YANGI" && composer install --no-dev --optimize-autoloader --no-interaction

echo "==> Umumiy kataloglar"
ln -sfn "$ILDIZ/umumiy/.env" "$YANGI/.env"
rm -rf "$YANGI/storage" && ln -sfn "$ILDIZ/umumiy/storage" "$YANGI/storage"

echo "==> Migratsiyalar"
php "$YANGI/tools/migrate.php"

echo "==> Salomatlik tekshiruvi"
php "$YANGI/tools/salomatlik.php"

echo "==> Almashtirish"
ln -sfn "$YANGI" "$ILDIZ/joriy"
sudo systemctl reload php8.3-fpm

echo "==> Eski relizlarni tozalash"
cd "$ILDIZ/relizlar" && ls -1t | tail -n +6 | xargs -r rm -rf

echo "==> Tayyor: $RELIZ"
Terminal
#!/bin/bash
# orqaga.sh
ILDIZ=/var/www/kutubxona
OLDINGI=$(ls -1t "$ILDIZ/relizlar" | sed -n '2p')

ln -sfn "$ILDIZ/relizlar/$OLDINGI" "$ILDIZ/joriy"
sudo systemctl reload php8.3-fpm

echo "Qaytarildi: $OLDINGI"

Bosqich 9 - hujjatlar #

MARKDOWN
# Kutubxona tizimi

Maktab kutubxonasi uchun kitob va ijara hisobini yuritish tizimi.

## Tez boshlash

    git clone [email protected]:maktab/kutubxona.git
    cd kutubxona
    cp .env.example .env
    docker compose up -d
    docker compose exec ilova php tools/migrate.php
    docker compose exec ilova php tools/seed.php

Sayt: http://localhost:8080
Kirish: [email protected] / sinov123

## Talablar

| Dastur | Versiya |
|--------|---------|
| PHP | 8.2+ |
| MySQL | 8.0+ |

## Asosiy tushunchalar

- **Kitob** - bibliografik yozuv (nom, muallif)
- **Nusxa** - jismoniy kitob, inventar raqami bilan
- **Ijara** - nusxaning o'quvchiga berilishi

Bitta kitobning bir necha nusxasi bo'lishi mumkin.

## Biznes qoidalari

- Ijara muddati: 14 kun
- Uzaytirish: bir marta, 7 kunga
- Kechikkan ijara uzaytirilmaydi
- Bir o'quvchida bir vaqtda 3 tadan ko'p kitob bo'lmaydi
- Kechikkan kitobi bor o'quvchiga yangi kitob berilmaydi

## Hujjatlar

- [Arxitektura](hujjatlar/arxitektura.md)
- [Deploy](hujjatlar/deploy.md)
- [Ish daftarchalari](hujjatlar/ish-daftarchalari/)

Bosqich 10 - natijalar #

Loyihadan oldin va keyin Oldin (daftar) Kitob qidirish: 3-10 daqiqa Kechikkanlar ro'yxati: 2 soat Ijara qayd etish: 2 daqiqa Sinf hisoboti: 3 soat Xato ehtimoli: yuqori Oyiga ~12 soat qo'shimcha ish Keyin (tizim) Kitob qidirish: 3 soniya Kechikkanlar ro'yxati: 1 soniya Ijara qayd etish: 20 soniya Sinf hisoboti: 2 soniya Xato ehtimoli: past Oyiga ~11 soat tejaladi Texnik ko'rsatkichlar emas, biznes natijasi muhim
Loyiha muvaffaqiyati foydalanuvchi tejagan vaqt bilan o'lchanadi
MARKDOWN
## Texnik natijalar

| Ko'rsatkich | Qiymat |
|-------------|--------|
| Kod satrlari | ~4200 |
| Testlar soni | 87 |
| Qoplama | 82% (domen: 96%) |
| CI vaqti | 3 daqiqa 40 soniya |
| Qidiruv (3000 kitob) | 45 ms |
| Sahifa yuklanishi (p95) | 380 ms |
| Deploy vaqti | 90 soniya |

## Muddat

| Bosqich | Rejalashtirilgan | Haqiqiy |
|---------|-----------------|---------|
| Talablar | 1 hafta | 1.5 hafta |
| Arxitektura | 0.5 hafta | 0.5 hafta |
| Ishlab chiqish | 4 hafta | 5 hafta |
| Testlash | 1 hafta | 0.5 hafta |
| Deploy va o'qitish | 0.5 hafta | 1 hafta |
| **Jami** | **7 hafta** | **8.5 hafta** |

Kechikish sababi: talablar aniqlashtirilishi kutilganidan uzoq
davom etdi (CSV import formati bo'yicha 3 marta o'zgarish).

Retrospektiva #

MARKDOWN
# Loyiha retrospektivasi

## Nima yaxshi ketdi

+ TDD domen qatlamida - biznes qoidalarida bironta xato bo'lmadi
+ Modulli tuzilma - yangi hisobot qo'shish 2 soat oldi
+ Docker - lokal muhitni sozlash 5 daqiqa
+ Foydalanuvchi bilan haftalik uchrashuvlar - noto'g'ri tushunish bo'lmadi
+ Erta deploy - 3-haftadan boshlab sinov muhitida ishladi

## Nima yomon ketdi

- CSV import talablari 3 marta o'zgardi (1 hafta yo'qotildi)
- Frontend testlari yozilmadi - qo'lda tekshirishga vaqt ketdi
- Zaxira nusxa faqat oxirida sozlandi
- Foydalanuvchi qo'llanmasi kech yozildi

## Nimani boshqacha qilamiz

1. Format talab qiladigan imkoniyatlar uchun **namuna fayl**
   oldindan so'raladi
2. Frontend uchun kamida asosiy E2E testlar bo'ladi
3. Zaxira nusxa **birinchi haftada** sozlanadi
4. Qo'llanma imkoniyat bilan **birga** yoziladi

## Saqlanadigan amaliyotlar

- Domen qatlamida TDD
- Haftalik demo
- ADR yozish
- Erta va tez-tez deploy

Yakuniy tekshiruv ro'yxati #

Loyiha tugadi deb hisoblanadi, agar
TalabBajarildi
Barcha "Must" talablar bajarilgan
Testlar yozilgan va o'tadi
CI quvuri ishlaydi
Deploy avtomatlashtirilgan
Orqaga qaytarish sinovdan o'tgan
Zaxira nusxa ishlaydi va tiklash sinalgan
Monitoring va ogohlantirishlar sozlangan
README to'liq
Arxitektura hujjati yozilgan
Ish daftarchalari tayyor
Foydalanuvchi qo'llanmasi bor
Xavfsizlik tekshiruvi o'tkazilgan
Foydalanuvchilar o'qitilgan
Qo'llab-quvvatlash jarayoni kelishilgan
Yakuniy topshiriq

Butun tsiklni o'z loyihangizda takrorlang:

  1. Muammoni tanlang va 10 ta aniqlashtiruvchi savol yozing.
  2. Funksional va nofunksional talablarni shakllantiring.
  3. MoSCoW bo'yicha ustuvorlashtiring, "Won't" ni ham yozing.
  4. Uchta foydalanuvchi hikoyasi va qabul mezonlarini yozing.
  5. Domen modelini loyihalang (kamida 4 klass).
  6. TDD bilan biznes qoidalarini yozing (kamida 15 test).
  7. Qatlamli arxitektura tuzilmasini yarating.
  8. Baza sxemasini yozing: kalitlar, indekslar, cheklovlar bilan.
  9. Kamida 3 ta ADR yozing.
  10. CI quvurini sozlang: lint, statik tahlil, testlar.
  11. Deploy va orqaga qaytarish skriptlarini yozing.
  12. To'liq README va arxitektura hujjatini yozing.
  13. Bitta ish daftarchasi yozing.
  14. Xavfsizlik tekshiruv ro'yxatini bajaring.
  15. Retrospektiva o'tkazing va xulosalar yozing.

Yakuniy xulosa #

Yigirma bo'lim davomida siz quyidagilarni o'rgandingiz:

  • Muhandislik va dasturlash farqi, sifat mezonlari
  • Ishlab chiqish modellari: sharshara, V-model, iterativ, Agile
  • Talablarni yig'ish: funksional va nofunksional, hikoyalar, mezonlar
  • Scrum: rollar, artefaktlar, marosimlar, baholash
  • Rejalashtirish: PERT, uchburchak cheklovlari, xavflar
  • Arxitektura: qatlamlar, monolit, modulli monolit, ADR
  • Loyihalash naqshlari: Strategiya, Fabrika, Adapter, Dekorator
  • SOLID va toza kod prinsiplari
  • UML va C4 modeli
  • Domen modeli va ma'lumotlar bazasini loyihalash
  • API loyihalash: REST, holat kodlari, versiyalash, OpenAPI
  • Testlash: piramida, modul va integratsiya testlari, soxta obyektlar
  • TDD: qizil-yashil-refaktor
  • Kod tekshiruvi va statik tahlil
  • Refaktoring va texnik qarzni boshqarish
  • Jamoaviy ish: Konvey qonuni, muloqot, navbatchilik
  • CI/CD: quvurlar, deploy strategiyalari, monitoring
  • Xavfsizlik: OWASP, sirlar, ishonchlilik naqshlari
  • Hujjatlar: README, ADR, ish daftarchalari
Eng muhim xulosalar
  1. Muammoni tushunish - yechimdan muhimroq
  2. Kod odamlar uchun yoziladi, mashina uchun emas
  3. Testlar tezlikni saqlaydi, sekinlashtirmaydi
  4. Sodda yechim - deyarli har doim to'g'ri boshlang'ich
  5. Muloqot texnik mahoratdan kam ahamiyatli emas
  6. Hujjat - kelajakdagi o'zingizga yozilgan xat
  7. Kichik qadamlar katta sakrashdan xavfsizroq
  8. O'zgarish normal - unga tayyor bo'ling
Keyingi qadamlar
Yo'nalishNima o'rganish
ArxitekturaDDD, hodisalarga asoslangan tizimlar, CQRS
DevOpsKubernetes, Terraform, kuzatuvchanlik
SifatIlg'or testlash, mutatsiya testlari, xususiyat testlari
RahbarlikTexnik yetakchilik, mentorlik, arxitektura boshqaruvi
SohaYuqori yuklamali tizimlar, xavfsizlik, ma'lumotlar

Eng muhimi: haqiqiy loyihalarda ishlang. Kitob bilim beradi, amaliyot esa mahorat.

Foydali kitoblar
KitobMuallif
Clean CodeRobert Martin
RefactoringMartin Fowler
Domain-Driven DesignEric Evans
The Pragmatic ProgrammerHunt, Thomas
Working Effectively with Legacy CodeMichael Feathers
Designing Data-Intensive ApplicationsMartin Kleppmann
AccelerateForsgren, Humble, Kim

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.