← Назад к вопросам

Когда лучше использовать монолит?

2.0 Middle🔥 111 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда лучше использовать монолит

Это архитектурный вопрос, проверяющий умение выбирать подходящую архитектуру. Честный ответ: монолит часто лучше, чем микросервисы, но это зависит от множества факторов.

Определения

Монолит — одно приложение, где весь код находится в одном репозитории и развёртывается вместе.

Микросервисы — множество независимых сервисов, каждый со своим репозиторием и деплоем.

Монолит:
┌────────────────────────────────┐
│  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-ы)

Золотое правило: Начни с монолита. Переходи на микросервисы только, когда монолит становится реальным узким местом, поддерживаемым данными и метриками.