20-bo‘lim
Amaliy loyiha - kutubxona tizimi
Talablardan deploy gacha - barcha bilimlarni birlashtirgan to'liq muhandislik tsikli.
Ushbu bo‘lim mundarijasi
- Bosqich 1 - muammo va talablar
- Aniqlashtiruvchi savollar
- Talablar
- Foydalanuvchi hikoyalari
- Bosqich 2 - domen modeli
- Bosqich 3 - testlar (TDD)
- Bosqich 4 - arxitektura
- Bosqich 5 - baza sxemasi
- Bosqich 6 - hisobotlar
- Bosqich 7 - CI quvuri
- Bosqich 8 - deploy
- Bosqich 9 - hujjatlar
- Bosqich 10 - natijalar
- Retrospektiva
- Yakuniy tekshiruv ro'yxati
- Yakuniy xulosa
Yigirma bo'lim davomida o'rgangan hamma narsani bitta loyihada qo'llaymiz - g'oyadan ishlab chiqarishgacha.
Bosqich 1 - muammo va talablar #
*"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 #
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
"Nechta kitob?" degan savol arxitekturani belgilaydi:
- 3000 kitob → oddiy MySQL yetarli, murakkab qidiruv dvigateli kerak emas
- 3 million kitob → butunlay boshqa yechim
Talablar #
## 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 #
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 #
// 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(),
));
}
}
// 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 sifatidadate() yoki new DateTime() ni domen ichida chaqirmang.
Vaqtni tashqaridan bering - shunda testda istalgan sanani o'rnatish mumkin:
$ijara->uzaytiring(new DateTimeImmutable('2026-09-01'));
Aks holda "kechikkan ijara" testini yozib bo'lmaydi.
Bosqich 3 - testlar (TDD) #
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 #
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/
// 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 #
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:
SELECT * FROM ijaralar
WHERE qaytarilgan IS NULL AND qaytarish_muddati < CURDATE();
Indeks aynan shu so'rov uchun tuzilgan.
Bosqich 6 - hisobotlar #
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(),
);
}
}
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 #
# .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 #
#!/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"
#!/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 #
# 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 #
## 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 #
# 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 #
| Talab | Bajarildi |
|---|---|
| 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 |
Butun tsiklni o'z loyihangizda takrorlang:
- Muammoni tanlang va 10 ta aniqlashtiruvchi savol yozing.
- Funksional va nofunksional talablarni shakllantiring.
- MoSCoW bo'yicha ustuvorlashtiring, "Won't" ni ham yozing.
- Uchta foydalanuvchi hikoyasi va qabul mezonlarini yozing.
- Domen modelini loyihalang (kamida 4 klass).
- TDD bilan biznes qoidalarini yozing (kamida 15 test).
- Qatlamli arxitektura tuzilmasini yarating.
- Baza sxemasini yozing: kalitlar, indekslar, cheklovlar bilan.
- Kamida 3 ta ADR yozing.
- CI quvurini sozlang: lint, statik tahlil, testlar.
- Deploy va orqaga qaytarish skriptlarini yozing.
- To'liq README va arxitektura hujjatini yozing.
- Bitta ish daftarchasi yozing.
- Xavfsizlik tekshiruv ro'yxatini bajaring.
- 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
- Muammoni tushunish - yechimdan muhimroq
- Kod odamlar uchun yoziladi, mashina uchun emas
- Testlar tezlikni saqlaydi, sekinlashtirmaydi
- Sodda yechim - deyarli har doim to'g'ri boshlang'ich
- Muloqot texnik mahoratdan kam ahamiyatli emas
- Hujjat - kelajakdagi o'zingizga yozilgan xat
- Kichik qadamlar katta sakrashdan xavfsizroq
- O'zgarish normal - unga tayyor bo'ling
| Yo'nalish | Nima o'rganish |
|---|---|
| Arxitektura | DDD, hodisalarga asoslangan tizimlar, CQRS |
| DevOps | Kubernetes, Terraform, kuzatuvchanlik |
| Sifat | Ilg'or testlash, mutatsiya testlari, xususiyat testlari |
| Rahbarlik | Texnik yetakchilik, mentorlik, arxitektura boshqaruvi |
| Soha | Yuqori yuklamali tizimlar, xavfsizlik, ma'lumotlar |
Eng muhimi: haqiqiy loyihalarda ishlang. Kitob bilim beradi, amaliyot esa mahorat.
| Kitob | Muallif |
|---|---|
| Clean Code | Robert Martin |
| Refactoring | Martin Fowler |
| Domain-Driven Design | Eric Evans |
| The Pragmatic Programmer | Hunt, Thomas |
| Working Effectively with Legacy Code | Michael Feathers |
| Designing Data-Intensive Applications | Martin Kleppmann |
| Accelerate | Forsgren, Humble, Kim |
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.