Когда лучше использовать монолит?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Когда лучше использовать монолит
Это архитектурный вопрос, проверяющий умение выбирать подходящую архитектуру. Честный ответ: монолит часто лучше, чем микросервисы, но это зависит от множества факторов.
Определения
Монолит — одно приложение, где весь код находится в одном репозитории и развёртывается вместе.
Микросервисы — множество независимых сервисов, каждый со своим репозиторием и деплоем.
Монолит:
┌────────────────────────────────┐
│ FastAPI приложение │
│ ├── User модуль │
│ ├── Order модуль │
│ ├── Payment модуль │
│ └── Notification модуль │
│ │
│ Одна БД, один процесс │
└────────────────────────────────┘
Микросервисы:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ User Service │ │ Order Service │ │ Payment Service │
│ │ │ │ │ │
│ Своя БД │ │ Своя БД │ │ Своя БД │
│ Свой код │ │ Свой код │ │ Свой код │
│ Свой деплой │ │ Свой деплой │ │ Свой деплой │
└──────────────────┘ └──────────────────┘ └──────────────────┘
↓ ↓ ↓
REST API REST API REST API
Преимущества монолита
1. Простота разработки
Все находится в одном месте:
# Монолит: всё в одной папке
app/
├── models/
│ ├── user.py
│ ├── order.py
│ └── payment.py
├── services/
│ ├── user_service.py
│ ├── order_service.py
│ └── payment_service.py
├── api/
│ ├── users.py
│ ├── orders.py
│ └── payments.py
└── main.py
# Создать feature просто: добавь код в папку, всё работает
2. Простота отладки
# Монолит: всё в процессе, легко отлаживать
def create_order(user_id: int, items: List[Item]):
# Логирование работает везде
logger.info(f"Creating order for user {user_id}")
user = get_user(user_id) # Прямой вызов функции
order = Order.create(user, items) # Прямой вызов
# Может выполниться любое действие, стек легко трейсится
payment = process_payment(order) # Прямой вызов
return order
# При ошибке видишь весь стек вызовов в одном месте
# Debugger может останавливаться на любой строке
3. Производительность
Нет сетевой задержки между сервисами:
# Монолит: вызов функции = микросекунды
def get_user_with_orders(user_id: int):
user = User.get(user_id) # SQL запрос
orders = Order.query.filter_by(user_id=user_id).all() # SQL запрос
return {"user": user, "orders": orders} # 2 SQL, готово
# Общее время: 50ms
# Микросервисы: вызов сервиса = 50-200ms
def get_user_with_orders(user_id: int):
user = requests.get(f"http://user-service/users/{user_id}") # HTTP 100ms
orders = requests.get(f"http://order-service/users/{user_id}/orders") # HTTP 100ms
return {"user": user, "orders": orders} # 2 HTTP, 200ms+
# Общее время: 200ms+ (в 4 раза медленнее!)
4. Простота транзакций
# Монолит: ACID транзакции просто работают
with db.transaction():
user = User.create(name="John", email="john@example.com")
order = Order.create(user_id=user.id, total=100)
payment = Payment.create(order_id=order.id, amount=100)
# Если что-то сломается, всё откатится
# ACID гарантирует корректность
# Микросервисы: нужна Saga паттерн, это сложно
from celery import Task
class CreateOrderSaga(Task):
def run(self, user_id, items):
try:
# Создаём заказ
order_response = requests.post(
"http://order-service/orders",
json={"user_id": user_id, "items": items}
)
order_id = order_response.json()["id"]
# Обрабатываем платёж
payment_response = requests.post(
"http://payment-service/payments",
json={"order_id": order_id, "amount": 100}
)
if payment_response.status_code != 200:
# Откатываем заказ
requests.delete(f"http://order-service/orders/{order_id}")
raise Exception("Payment failed")
except Exception as e:
# Нужна логика компенсации
self.retry(exc=e, countdown=60)
5. Развёртывание
Одно приложение = простой деплой:
# Монолит
git push # Одна команда, всё развёртывается
# 1 Docker контейнер, 1 процесс, готово
# Микросервисы
# Нужно:
# - Развернуть User Service
# - Развернуть Order Service
# - Развернуть Payment Service
# - Обновить API Gateway
# - Проверить всех работают вместе
# Намного сложнее и больше точек отказа
Недостатки монолита
1. Масштабирование команды затруднено
# Монолит: все работают в одном коде базе
# 5 разработчиков = много конфликтов в git
# Сложнее разделить ответственность
# Микросервисы: разные команды, разные сервисы
# Team A работает на User Service
# Team B работает на Order Service
# Они независимы, конфликтов нет
2. Сложность с разными технологиями
# Монолит: всё на Python/FastAPI
app = FastAPI()
# Если часть нужна на Go (для скорости), или Rust
# Всё равно остаётся в Python монолите
# Нельзя выбрать лучший язык для каждой части
# Микросервисы: каждый сервис своим языком
# User Service: Python (простой)
# Order Service: Go (быстрый)
# Payment Service: Rust (супер быстрый)
3. Один баг может упалить всё приложение
# Монолит: один процесс
app = FastAPI()
@app.get("/orders/{order_id}")
async def get_order(order_id: int):
# Баг: infinite loop
while True:
pass
# Один обработчик с багом = весь сервис упал
# Даже user API перестанет работать
# Микросервисы: изолированы
# Order Service упал
# User Service продолжает работать
# Payment Service продолжает работать
4. Сложность обновления
# Монолит: нужно перезапустить всё приложение
while users_connected:
# Нельзя обновить, пока кто-то использует
wait()
# Обновление часто требует downtime
# Микросервисы: обновляем по одному
# User Service: обновляем, Order Service работает
# Order Service: обновляем, User Service работает
# Можно делать rolling updates без downtime
Когда использовать монолит? (BEST PRACTICES)
1. Стартап (первые 6-12 месяцев)
# Стартап, нужно быстро валидировать идею
# Монолит = быстрее разработка
from fastapi import FastAPI
app = FastAPI()
# Все features в одном приложении
@app.get("/users")
async def list_users():
...
@app.post("/orders")
async def create_order():
...
@app.post("/payments")
async def process_payment():
...
# Через 6 месяцев, если успешно, можно переходить на микросервисы
2. Команда менее 6-8 человек
Малая команда (3-5 разработчиков):
→ Монолит оптимален
→ Легко синхронизироваться
→ Быстрая разработка
Большая команда (20+ разработчиков):
→ Микросервисы лучше
→ Разные команды, разные сервисы
→ Меньше конфликтов
3. Бизнес-критичные требования к консистентности
# Банковское приложение, платёжная система
# Нужны ACID транзакции
# Монолит = просто
with db.transaction():
account1.balance -= 100
account2.balance += 100
db.commit() # Либо оба изменились, либо ничего
# Микросервисы = сложно (нужен Saga паттерн)
4. Бизнес-домен небольшой и хорошо определён
Монолит подходит:
- Блог (Users, Posts, Comments)
- E-shop (Products, Orders, Payments)
- CRM (Contacts, Deals, Activities)
Микросервисы подходят:
- Сложная система (10+ независимых доменов)
- Meta/Google/Netflix (гигантские системы)
5. Нет требований к независимому масштабированию
# Монолит
# Если один компонент требует 10x масштабирования
# Масштабируем всё приложение (даже то, что не нужно)
kubernetes_replicas = 10 # Запускаем 10 инстансов
# Все модули (user, order, payment) масштабируются вместе
# Микросервисы
user_service_replicas = 2
order_service_replicas = 10 # Только order нужна масштабка
payment_service_replicas = 1
Когда переходить с монолита на микросервисы?
Признаки, что монолит становится узким местом:
1. Команда выросла более чем на 20 разработчиков
2. Главная ветка (main) часто имеет конфликты
3. Один деплой в день становится нормой
4. Части приложения нужно масштабировать независимо
5. Разные части требуют разных технологий
6. Каждое обновление требует полного тестирования
7. Один баг в одном модуле может упалить весь сервис
Правильный путь миграции:
Фаза 1: Монолит (0-6 месяцев)
↓
Фаза 2: Модульный монолит (6-18 месяцев)
(Разделяем логику на четкие слои)
↓
Фаза 3: Постепенная миграция на микросервисы (18+ месяцев)
(Один сервис за раз)
- Создаём Order Service
- Создаём Payment Service
- Остаток в User Service
↓
Фаза 4: Полная микросервисная архитектура
Реальный пример: что я выбираю
# Проект для клиента: SaaS приложение
# 3 разработчика, нужно быстро запустить
# MVP за 2 месяца
МОЙ ВЫБОР: МОНОЛИТ
from fastapi import FastAPI
from sqlalchemy import create_engine
app = FastAPI()
engine = create_engine("postgresql://...")
# Все фичи в одном приложении
@app.get("/api/v1/users")
@app.post("/api/v1/organizations")
@app.put("/api/v1/subscriptions/{id}")
# ...
# Развёртывание: docker push, готово
# Разработка: всё в одном git repos
# Тестирование: один pytest
# За 2 месяца запустили MVP
# За 6 месяцев требуется масштабирование
# ПОТОМ (в месяце 8):
МИГРАЦИЯ НА МИКРОСЕРВИСЫ
# Выделяем Payment Service
# Выделяем Notification Service
# Остаток в Core Service
Резюме
Используй монолит:
- Стартап и первые 6-12 месяцев
- Команда менее 8 разработчиков
- Требования к ACID транзакциям
- Бизнес-домен хорошо определён
- Нет требований к независимому масштабированию
Переходи на микросервисы:
- Команда выросла более чем на 20 разработчиков
- Части требуют независимого масштабирования
- Нужны разные технологии для разных частей
- Организационная структура поддерживает (разные team-ы)
Золотое правило: Начни с монолита. Переходи на микросервисы только, когда монолит становится реальным узким местом, поддерживаемым данными и метриками.