Publish/Subscribe · Loose coupling · Event-driven arxitektura · EventBus
11-sinfProfessional IT4-hafta · 10-darsSubject va Observer rollari, publish/subscribe tamoyili va bo'sh bog'liqlik.
Hodisalarga asoslangan arxitektura, xabarlar oqimi va xotira xatarlari.
Python'da markaziy EventBus tizimi va soxta kuzatuvchi (mock) bilan sinash.
Qattiq bog'liqlikdan (Tight Coupling) mustaqil xabarnomalarga o'tish
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)
complete_orderni testlash uchun barcha xizmatlarni soxtalashtirish talab etiladi.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.")
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}")
Monolitdan ko'p qatlamli va mikroxizmatli tizimlarga o'tish ko'prigi
Kuzatuvchilar to'g'ridan-to'g'ri bitta Subject obyektiga yopishadi (attach).
Barcha hodisalar markaziy magistral (broker) orqali e'lon qilinadi va tarqatiladi.
"user:created", "order:paid".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"})
Kuzatuvchi obyekt o'chirilganda unsubscribe qilinmasa, u Subject ro'yxatida qolib GC tomonidan tozalanmaydi.
Kuzatuvchilarning chaqirilish ketma-ketligiga suyanmang. Ular bir-biriga bog'liq bo'lmasligi shart.
A hodisasi B ni chaqirib, B hodisasi yana A ni chaqirsa cheksiz rekursiya yuzaga keladi.
Hodisalarni haqiqiy tashqi xizmatlarsiz 100% mustaqil tekshirish
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)
Kuzatuvchi tugamaguncha kutiladi. Juda sodda, lekin jarayon yiqilsa hodisa yo‘qoladi.
E’lon qilish tez, kuzatuvchi mustaqil. Navbat hodisani saqlaydi. Infratuzilma kerak: RabbitMQ, Kafka, SQS.
unsubscribe / weak reference
Tartib kafolatlanmaydi (Kafka: faqat bitta partition ichida)
Idempotentlik: event-id bo‘yicha deduplikatsiya
try/except + log yoki DLQ
Bitta hodisaga bir nechta mustaqil javob; javob beruvchilar soni o‘zgarib turadi; UI komponentlari
Bitta qabul qiluvchi, bitta aniq harakat, 5 qatorli skript: oddiy funksiya chaqirish yetarli
Andoza muammoni hal qilsin, murakkablik qo‘shmasin (over-engineering)
Komponent yopildi, lekin unsubscribe chaqirilmadi. Nima bo‘ladi?
unsubscribe yoki weak reference.book:borrowed hodisasiO'quvchi kutubxonadan kitob olganida tizimda 3 ta mustaqil reaksiya bo'lishi kerak:
logs/library.log fayliga voqeani yozadi.Kuzatuvchi ichida xatolik yuz bersa nima bo'ladi?
notify() ichida try/except yo'q. 1-kuzatuvchi yiqilsa, qolgan 4 ta kuzatuvchi hech qachon xabar olmaydi.
Har bir kuzatuvchi chaqiruvi alohida izolyatsiya qilinadi. Xato qayd etiladi (log), ammo zanjir davom etadi.
Subject va Observer bir-birining ichki tuzilishini bilmaydi, faqat umumiy kontraktga suyanadi.
Yangi kuzatuvchilar mavjud kodni o'zgartirmasdan tizimga qo'shiladi va chiqariladi.
11-darsda backend dunyosining asosi — REST API va HTTP protokoliga kirishamiz.