Как устроены системы: от монолита до событий
Шаблон организации компонентов и их взаимодействия
Как выбрать?
Мы рассмотрим:
| Messaging | Event-Driven, Pub/Sub |
| Distributed | Client-Server, P2P |
| Structural | Monolithic, Layered, Component-Based |
Система реагирует на события
Произошло событие → система реагирует
Примеры: пользователь зарегистрировался, модель обучилась
# Событие: данные обновились
event_bus.publish("data_updated", dataset_id)
# Обработчик 1: запускает переобучение
@on_event("data_updated")
def retrain_model(event):
train_new_version(event.dataset_id)
# Обработчик 2: уведомляет команду
@on_event("data_updated")
def notify_team(event):
send_slack("Новые данные — модель будет переобучена")
— Микросервисы
— Реактивные системы
— ETL, ML Pipelines
Один издатель → много подписчиков
Pub/Sub — это способ доставки событий
Publisher → Message Broker → [Subscriber A, Subscriber B]
# Издатель
redis.publish("logs", "User logged in")
# Подписчик 1
pubsub = redis.pubsub()
pubsub.subscribe("logs")
for message in pubsub.listen():
print("LOG:", message['data'])
# Подписчик 2 — другой сервис
pubsub.subscribe("logs")
for msg in pubsub.listen():
analytics.track(msg)
— Логгирование
— Аналитика
— Push-уведомления
Один сервер обслуживает множество клиентов
Клиент → GET /api/predict
Сервер → { "result": 0.92 }
@app.post("/predict")
def predict(input: Features):
result = model.predict(input.values)
return {"prediction": result}
Клиенты: фронтенд, мобильное приложение, скрипт
— Веб-API
— Инференс-сервисы
— Централизованные системы
Все узлы равны — нет центрального сервера
[Node A] ↔ [Node B] ↔ [Node C]
↖_____________↙
Каждый может быть клиентом и сервером
# Узел обучает локально
local_model = train_on_local_data()
# Отправляет обновление соседям
send_update_to_peers(local_model.delta)
— Распределённые вычисления
— Файлообмен
— Конфиденциальное обучение (federated learning)
Всё в одном приложении
my-app/
├── main.py
├── models/
├── api/
├── workers/
└── database.py
Один процесс, одна база, одно развертывание
# Один файл — всё вместе
@app.post("/train")
def train_endpoint():
data = load_data()
model = train(data)
save_model(model)
send_email()
— Небольшие проекты
— MVP
— Команда из 1–3 человек
Слои: сверху вниз — UI, Use Cases, Domain, Infrastructure
┌─────────────────┐
│ Presentation │ ← API, CLI
├─────────────────┤
│ Application │ ← Use Cases
├─────────────────┤
│ Domain │ ← User, Model, TrainingJob
├─────────────────┤
│ Infrastructure │ ← DB, Email, HTTP
└─────────────────┘
↓
Зависимости только вниз!
app/
├── presentation/
│ └── api.py
├── application/
│ └── usecases.py
├── domain/
│ └── models.py
└── infrastructure/
├── database.py
└── email_client.py
— Большие приложения
— Команды > 3 человек
— Нужна долгосрочная поддерживаемость
Система как набор независимых компонентов
Компоненты группируются по домену, а не по типу
user/
├── models.py
├── service.py
└── api.py
order/
├── models.py
├── service.py
└── discount_calculator.py
— Подготовка к микросервисам
— Большие системы
— Domain-Driven Design
| Стиль | Масштабируемость | Сложность | Где используется |
|---|---|---|---|
| Monolithic | Низкая | Низкая | MVP, малые команды |
| Layered | Средняя | Средняя | Большие приложения |
| Component-Based | Высокая | Высокая | DDD, микросервисы |
| Event-Driven | Очень высокая | Очень высокая | Реактивные системы |
| Client-Server | Высокая | Низкая | Веб, API |
Не "лучше/хуже", а "подходит/не подходит"
Теперь вы понимаете, как устроены системы.
Следующие темы:
"Архитектура — это не про технологии.
Это про принятие решений."