Блог AST-SoftPro
Архитектурные паттерны для бизнес-приложений: монолит, микросервисы, серверлесс
Архитектурные паттерны для бизнес-приложений: монолит, микросервисы, серверлесс
Выбор архитектурного паттерна — одно из самых важных решений на старте проекта. Ошибиться можно в любую сторону: слишком простая архитектура не выдержит роста, а слишком сложная создаст проблемы с самого начала. В этой статье разберём три основных подхода — монолит, микросервисы и серверлесс — и покажем, когда каждый из них оправдан.
Монолит: простой старт
Монолит — это единое приложение, где весь код, бизнес-логика и данные находятся в одном процессе. Все модули общаются через вызовы функций, а не через сеть.
Преимущества монолита:
-
Простота разработки. Один репозиторий, один процесс сборки, один деплой. Новому разработчику не нужно разбираться в инфраструктуре — достаточно запустить
pip install -r requirements.txtиpython main.py. -
Простота отладки. Все данные доступны в одном процессе. Можно поставить точку останова в любой функции и отследить весь путь выполнения.
-
Производительность. Вызовы функций внутри процесса быстрее сетевых запросов на порядки. Нет накладных расходов на сериализацию, маршрутизацию и сетевые задержки.
-
Транзакционная целостность. Все операции над данными выполняются в рамках одной транзакции базы данных. Не нужно реализовывать распределённые транзакции.
Пример монолита на FastAPI:
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base
app = FastAPI(title="Shop Monolith")
Base = declarative_base()
engine = create_engine("postgresql://localhost/shop")
Session = sessionmaker(bind=engine)
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True)
name = Column(String(255))
price = Column(Integer)
Base.metadata.create_all(engine)
@app.get("/products/{product_id}")
def get_product(product_id: int, db: Session = Depends(lambda: Session())):
return db.query(Product).filter(Product.id == product_id).first()
@app.post("/orders")
def create_order(order_data: dict, db: Session = Depends(lambda: Session())):
# Вся логика в одном месте
products = [db.query(Product).get(item["id"]) for item in order_data["items"]]
total = sum(p.price * item["qty"] for p, item in zip(products, order_data["items"]))
return {"total": total, "status": "created"}
Когда монолит подходит:
-
Команда до 10 разработчиков
-
Проект на стадии поиска продукт-маркет-фита
-
Нагрузка до 1000 RPS
-
Нет необходимости в независимом масштабировании компонентов
Микросервисы: масштабирование через разделение
Микросервисная архитектура предполагает разделение приложения на независимые сервисы, каждый из которых отвечает за конкретную бизнес-функцию и может развиваться отдельно.
Преимущества микросервисов:
-
Независимое масштабирование. Сервис обработки заказов можно масштабировать в 10 раз, не трогая сервис уведомлений.
-
Независимый деплой. Обновление одного сервиса не требует перезапуска всей системы.
-
Изоляция сбоев. Падение сервиса уведомлений не влияет на обработку заказов.
-
Технологическая гибкость. Разные сервисы могут быть написаны на разных языках — Python для ML-сервиса, Node.js для WebSocket-сервера.
Пример: разделение на сервисы
# Сервис заказов (orders-service)
from fastapi import FastAPI
import httpx
app = FastAPI(title="Orders Service")
@app.post("/orders")
async def create_order(order_data: dict):
# Вызов сервиса продуктов через HTTP
async with httpx.AsyncClient() as client:
products = await client.get(
f"http://products-service/api/products/{order_data['product_id']}"
)
return {"status": "created", "product": products.json()}
# Сервис продуктов (products-service)
from fastapi import FastAPI
app = FastAPI(title="Products Service")
@app.get("/api/products/{product_id}")
async def get_product(product_id: int):
return {"id": product_id, "name": "Product", "price": 999}
Когда микросервисы подходят:
-
Команда от 10+ разработчиков, работающих над разными частями системы
-
Разная нагрузка на компоненты (например, каталог читается в 10 раз чаще, чем заказы)
-
Необходимость независимого обновления частей системы
-
Готовность платить цену за сложность (сетевые задержки, распределённые транзакции, мониторинг)
Цена микросервисов:
-
Сложность отладки. Трейс запроса проходит через 3-5 сервисов. Нужна distributed tracing (Jaeger, OpenTelemetry).
-
Сетевые задержки. Каждый вызов между сервисами — сетевой запрос с задержкой 1-10 мс.
-
Распределённые транзакции. Вместо ACID-транзакций — eventual consistency и saga-паттерны.
-
Операционные расходы. Каждый сервис — отдельный процесс с собственным мониторингом, логированием и CI/CD.
Серверлесс: операционная лёгкость
Серверлесс-архитектура позволяет запускать функции без управления серверами. Вы платите только за время выполнения кода.
Преимущества серверлесса:
-
Нулевая операционная нагрузка. Не нужно управлять серверами, балансировщиками, масштабированием.
-
Автоматическое масштабирование. От 0 до 10 000 параллельных вызовов без вмешательства.
-
Платёж за использование. Если функция не вызывается — вы не платите.
-
Быстрый старт. Написание функции и деплой за минуты.
Пример серверлесс-функции на Python:
import json
import boto3
def lambda_handler(event, context):
"""Обработка заказа при поступлении уведомления"""
order_id = event["order_id"]
# Запись в DynamoDB
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("orders")
table.put_item(Item={"order_id": order_id, "status": "processing"})
# Отправка уведомления
sns = boto3.client("sns")
sns.publish(
TopicArn="arn:aws:sns:us-east-1:123456789:orders",
Message=json.dumps({"order_id": order_id}),
)
return {"statusCode": 200, "body": json.dumps({"status": "ok"})}
Когда серверлесс подходит:
-
Непредсказуемая нагрузка (пиковые нагрузки раз в неделю)
-
Фоновые задачи (обработка изображений, генерация отчётов)
-
Webhook-обработчики
-
MVP с минимальными операционными затратами
Ограничения серверлесса:
-
Cold start (холодный старт). Первый вызов после простоя может занять 1-5 секунд.
-
Ограничения по времени. Обычно максимум 15 минут на выполнение.
-
Ограничения по памяти. Обычно до 10 ГБ (AWS Lambda).
-
Vendor lock-in. Код привязан к провайдеру.
Сравнение паттернов
| Критерий | Монолит | Микросервисы | Серверлесс |
|---|---|---|---|
| Сложность разработки | Низкая | Высокая | Средняя |
| Операционная нагрузка | Средняя | Высокая | Низкая |
| Масштабируемость | Вертикальная | Горизонтальная | Автоматическая |
| Скорость разработки | Высокая | Средняя | Высокая |
| Стоимость при низкой нагрузке | Средняя | Высокая | Низкая |
| Стоимость при высокой нагрузке | Низкая | Средняя | Высокая |
| Отладка | Простая | Сложная | Средняя |
| Транзакции | ACID | Saga / eventual | Eventual |
Гибридные подходы
На практике редко используется чистый паттерн. Чаще встречаются гибриды:
Монолит + серверлесс для фоновых задач
Основное приложение — монолит, а фоновые задачи (отправка email, обработка изображений) — серверлесс-функции:
# Монолит: FastAPI
from fastapi import FastAPI
import httpx
app = FastAPI()
@app.post("/orders")
def create_order(order: dict):
# Сохраняем заказ
save_order(order)
# Вызываем серверлесс-функцию для обработки
httpx.post("https://api.gcp.run/notify-order", json=order)
return {"status": "queued"}
Микросервисы + серверлесс для пиковых нагрузок
Критические сервисы — микросервисы, а пиковые задачи (отправка уведомлений при распродаже) — серверлесс:
[API Gateway]
├── [Orders Service] (микросервис, 24/7)
├── [Products Service] (микросервис, 24/7)
├── [Notification Lambda] (серверлесс, по событию)
└── [Report Generator Lambda] (серверлесс, cron)
Как выбрать: практический алгоритм
-
Начинайте с монолита. Это не компромисс — это стратегия. Большинство проектов так и остаются монолитами.
-
Переходите к микросервисам, когда:
- Команда выросла до 10+ разработчиков
- Разные части системы обновляются с разной частотой
- Нагрузка на компоненты сильно различается
-
Монолит стал слишком медленным для сборки и деплоя
-
Используйте серверлесс для:
- Фоновых задач, которые не нужны в реальном времени
- Пиковых нагрузок, которые нельзя предсказать
-
Webhook-обработчиков и интеграций
-
Не делайте микросервисы «на вырост». Сложность микросервисов — это постоянные расходы. Не платите за то, что вам не нужно.
Заключение
Архитектурный паттерн — это инструмент, а не цель. Монолит не устарел, микросервисы не панацея, серверлесс не замена всему. Правильный выбор зависит от размера команды, нагрузки, бюджета и операционных возможностей.
Главное правило: начинайте с самого простого решения, которое решает вашу задачу. Усложняйте архитектуру только тогда, когда простые решения перестают работать — и только для конкретной проблемы.
Ключевые моменты:
-
Монолит — лучший старт для большинства проектов. Простота, скорость разработки, лёгкая отладка.
-
Микросервисы оправданы при большой команде и разной нагрузке на компоненты, но требуют инфраструктуры и экспертизы.
-
Серверлесс идеален для фоновых задач и пиковых нагрузок — нулевая операционная нагрузка.
-
Гибридные подходы (монолит + серверлесс) часто эффективнее чистых паттернов.
-
Усложняйте архитектуру только при реальных проблемах, а не «на вырост».