AXV

Loyihalash andozalari: Observer va hodisalar

Publish/Subscribe · Loose coupling · Event-driven arxitektura · EventBus

11-sinfProfessional IT4-hafta · 10-dars

Bugungi dars rejasi

01. Observer andozasi

Subject va Observer rollari, publish/subscribe tamoyili va bo'sh bog'liqlik.

02. Event-driven dizayn

Hodisalarga asoslangan arxitektura, xabarlar oqimi va xotira xatarlari.

03. EventBus va Testlar

Python'da markaziy EventBus tizimi va soxta kuzatuvchi (mock) bilan sinash.

01

Observer (Kuzatuvchi) andozasi

Qattiq bog'liqlikdan (Tight Coupling) mustaqil xabarnomalarga o'tish

Muammo: Qattiq bog'liqlik (Tight Coupling)

class OrderService:
    def complete_order(self, order_id):
        # 1. Asosiy mantiq
        db.save_status(order_id, "PAID")

        # 2. Qattiq bog'langan vazifalar
        send_email(order_id)
        send_sms(order_id)
        track_analytics(order_id)
        update_bonus_points(order_id)

Nega bu yomon?

  • OCP buziladi: Yangi harakat qo'shish uchun xizmat kodini tahrirlash shart.
  • Sinov qiyin: complete_orderni testlash uchun barcha xizmatlarni soxtalashtirish talab etiladi.
  • Xato zanjiri: SMS xizmati ishlamasa, butun buyurtma jarayoni to'xtashi mumkin.

Subject va Observer aloqasi

Subject (Publisher) OrderService + subscribe(observer) + notify(event_data) EmailNotifier update(order_id) AnalyticsTracker update(order_id) LoyaltyService update(order_id) Loose Coupling Subject kimlar ulanganini bilmaydi!

Observer interfeysi: Protocol orqali

from typing import Protocol, List

class OrderObserver(Protocol):
    """Barcha kuzatuvchilar uchun yagona shartnoma (kontrakt)."""
    def update(self, order_id: str, amount: float) -> None:
        ...

class EmailNotificationService:
    def update(self, order_id: str, amount: float) -> None:
        print(f"[Email] Buyurtma #{order_id} ({amount} so'm) uchun chek yuborildi.")

class BonusPointsService:
    def update(self, order_id: str, amount: float) -> None:
        points = int(amount // 10000)
        print(f"[Bonus] Mijozga {points} ball hisoblandi.")

Subject (Publisher) kodi

class OrderSubject:
    def __init__(self) -> None:
        self._observers: List[OrderObserver] = []

    def attach(self, observer: OrderObserver) -> None:
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer: OrderObserver) -> None:
        self._observers.remove(observer)

    def notify(self, order_id: str, amount: float) -> None:
        for observer in self._observers:
            try:
                observer.update(order_id, amount)
            except Exception as e:
                # Bitta kuzatuvchi xatosi boshqalarni to'xtatmaydi!
                print(f"Xatolik kuzatuvchida: {e}")
02

Event-Driven arxitektura va EventBus

Monolitdan ko'p qatlamli va mikroxizmatli tizimlarga o'tish ko'prigi

Observer vs Centralized EventBus

Klassik Observer (1:N)

Kuzatuvchilar to'g'ridan-to'g'ri bitta Subject obyektiga yopishadi (attach).

  • Subject o'zi ro'yxatni boshqaradi.
  • Kichik va bitta modul ichida qulay.
  • Modullar ko'paysa o'zaro bog'liqlik oshishi mumkin.

EventBus (N:M)

Barcha hodisalar markaziy magistral (broker) orqali e'lon qilinadi va tarqatiladi.

  • Nomli hodisalar: "user:created", "order:paid".
  • Publishers va Subscribers bir-birini umuman ko'rmaydi.
  • Zamonaviy Backend (Kafka, RabbitMQ, Redis) asosi.

EventBus arxitekturasi

OrderService AuthService EventBus "order:paid" "user:registered" publish(event, data) subscribe(event, handler) TelegramBot InvoicePDFGenerator WarehouseSync

Python'da oddiy EventBus kodi

from collections import defaultdict
from typing import Callable, Dict, List, Any

class EventBus:
    def __init__(self) -> None:
        self._subscribers: Dict[str, List[Callable[[Any], None]]] = defaultdict(list)

    def subscribe(self, event_name: str, handler: Callable[[Any], None]) -> None:
        self._subscribers[event_name].append(handler)

    def publish(self, event_name: str, payload: Any) -> None:
        for handler in self._subscribers.get(event_name, []):
            try:
                handler(payload)
            except Exception as err:
                print(f"[EventBus Xato] {event_name} -> {handler.__name__}: {err}")

# Foydalanish:
bus = EventBus()
bus.subscribe("book:borrowed", lambda data: print(f"Kitob berildi: {data['title']}"))
bus.publish("book:borrowed", {"title": "Clean Architecture", "user": "Ali"})

Hodisalar arxitekturasi xavflari

1. Xotira oqimi (Memory Leak)

Kuzatuvchi obyekt o'chirilganda unsubscribe qilinmasa, u Subject ro'yxatida qolib GC tomonidan tozalanmaydi.

2. Tartibsiz bajarilish

Kuzatuvchilarning chaqirilish ketma-ketligiga suyanmang. Ular bir-biriga bog'liq bo'lmasligi shart.

3. Siklik chaqiruvlar

A hodisasi B ni chaqirib, B hodisasi yana A ni chaqirsa cheksiz rekursiya yuzaga keladi.

03

Unit Test va Mock bilan sinash

Hodisalarni haqiqiy tashqi xizmatlarsiz 100% mustaqil tekshirish

Observer'ni Mock yordamida testlash

from unittest.mock import MagicMock
from order_publisher import OrderSubject

def test_order_subject_notifies_all_observers():
    # 1. Arrange: Subject va soxta kuzatuvchilar
    subject = OrderSubject()
    mock_email = MagicMock()
    mock_bonus = MagicMock()

    subject.attach(mock_email)
    subject.attach(mock_bonus)

    # 2. Act: Hodisani e'lon qilish
    subject.notify(order_id="ORD-101", amount=50000.0)

    # 3. Assert: Har bir mock chaqirilganini tekshirish
    mock_email.update.assert_called_once_with("ORD-101", 50000.0)
    mock_bonus.update.assert_called_once_with("ORD-101", 50000.0)

O‘xshatish: Telegram kanali

Kanal Subject: notify() Email Analytics Loyalty subscribe() / unsubscribe() - Subject kodi o‘zgarmaydi

Sinxron Observer yoki asinxron pub/sub?

Bitta jarayon ichida

Kuzatuvchi tugamaguncha kutiladi. Juda sodda, lekin jarayon yiqilsa hodisa yo‘qoladi.

Xizmatlar o‘rtasida (navbat / event bus)

E’lon qilish tez, kuzatuvchi mustaqil. Navbat hodisani saqlaydi. Infratuzilma kerak: RabbitMQ, Kafka, SQS.

Event-driven: navbat orqali

Producer message queue Consumer A Consumer B DLQ

Xavf va yechim. Bosing

Xotira oqimi

bosing

unsubscribe / weak reference

Tartibga ishonish

bosing

Tartib kafolatlanmaydi (Kafka: faqat bitta partition ichida)

Takroriy hodisa

bosing

Idempotentlik: event-id bo‘yicha deduplikatsiya

Xatoni yutish

bosing

try/except + log yoki DLQ

Observer qachon kerak, qachon ortiqcha?

Kerak

Bitta hodisaga bir nechta mustaqil javob; javob beruvchilar soni o‘zgarib turadi; UI komponentlari

Ortiqcha

Bitta qabul qiluvchi, bitta aniq harakat, 5 qatorli skript: oddiy funksiya chaqirish yetarli

Qoida

Andoza muammoni hal qilsin, murakkablik qo‘shmasin (over-engineering)

Mini-test

Komponent yopildi, lekin unsubscribe chaqirilmadi. Nima bo‘ladi?

Ro‘yxatda havola qolgani uchun obyekt tozalanmaydi. Yechim: unsubscribe yoki weak reference.

Mini-loyiha: «Kutubxona boshqaruvi»

book:borrowed hodisasi

O'quvchi kutubxonadan kitob olganida tizimda 3 ta mustaqil reaksiya bo'lishi kerak:

  • AuditLogger: logs/library.log fayliga voqeani yozadi.
  • StudentNotification: O'quvchiga qaytarish muddati (14 kun) haqida xabar jo'natadi.
  • InventoryTracker: Mavjud kitoblar zaxirasini bittaga kamaytiradi.

Tezkor savol: Tahlil qiling

Kuzatuvchi ichida xatolik yuz bersa nima bo'ladi?

Noto'g'ri yondashuv

notify() ichida try/except yo'q. 1-kuzatuvchi yiqilsa, qolgan 4 ta kuzatuvchi hech qachon xabar olmaydi.

To'g'ri yondashuv

Har bir kuzatuvchi chaqiruvi alohida izolyatsiya qilinadi. Xato qayd etiladi (log), ammo zanjir davom etadi.

Bugungi dars xulosasi

Bo'sh bog'liqlik

Subject va Observer bir-birining ichki tuzilishini bilmaydi, faqat umumiy kontraktga suyanadi.

OCP himoyasi

Yangi kuzatuvchilar mavjud kodni o'zgartirmasdan tizimga qo'shiladi va chiqariladi.

Keyingi dars

11-darsda backend dunyosining asosi — REST API va HTTP protokoliga kirishamiz.