Обзор микросервисной архитектуры
Эволюция архитектур
Монолит Модульный монолит Микросервисы
┌──────────┐ ┌──────────────────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ │ │ ┌────┐ ┌────┐ │ │ Svc │ │ Svc │ │ Svc │
│ All-in │ │ │Mod1│ │Mod2│ │ │ A │ │ B │ │ C │
│ -one │ │ └────┘ └────┘ │ │ │ │ │ │ │
│ │ │ ┌────┐ ┌────┐ │ │ DB │ │ DB │ │ DB │
│ Single │ │ │Mod3│ │Mod4│ │ │ A │ │ B │ │ C │
│ Deploy │ │ └────┘ └────┘ │ └─────┘ └─────┘ └─────┘
│ │ │ Single Deploy │ Independent Deploy
│ One DB │ │ One/Few DBs │ Independent DBs
└──────────┘ └──────────────────┘
Монолит
Все компоненты приложения в одном процессе, деплоятся как единый артефакт.
Преимущества монолита
- Простота разработки и отладки
- Вызовы между модулями -- обычные вызовы функций
- Одна БД -- простые транзакции и JOIN
- Простой деплой: один артефакт
- Рефакторинг через IDE (переименование, перемещение)
Проблемы монолита при росте
- Долгая сборка и запуск (10+ минут)
- Merge-конфликты при > 10 разработчиков
- Невозможно масштабировать отдельные компоненты
- Одна ошибка может уронить всё приложение
- Технологический lock-in: один язык, один фреймворк
<?php
declare(strict_types=1);
/**
* Monolith: all domains in one application
*/
// Controllers, services, repositories -- all in one process
final class OrderController
{
public function __construct(
private readonly OrderService $orderService,
private readonly PaymentService $paymentService, // Direct dependency
private readonly InventoryService $inventoryService, // Direct dependency
private readonly NotificationService $notificationService, // Direct dependency
) {}
public function createOrder(array $request): array
{
// All in one transaction -- simple and reliable
$this->db->beginTransaction();
try {
$order = $this->orderService->create($request);
$this->inventoryService->reserve($order->items); // Same process
$payment = $this->paymentService->charge($order); // Same process
$this->notificationService->sendConfirmation($order); // Same process
$this->db->commit();
return ['order_id' => $order->id];
} catch (\Throwable $e) {
$this->db->rollBack();
throw $e;
}
}
}
Модульный монолит
Монолит с чёткими границами между модулями. Каждый модуль имеет свой публичный API и может иметь свою схему в БД.
<?php
declare(strict_types=1);
/**
* Modular monolith: clear boundaries within single deployment
*/
// Module: Orders (public API only)
namespace App\Module\Orders;
interface OrderModuleApi
{
public function createOrder(CreateOrderCommand $command): OrderId;
public function getOrder(OrderId $id): OrderDto;
public function cancelOrder(OrderId $id): void;
}
// Module: Payments (public API only)
namespace App\Module\Payments;
interface PaymentModuleApi
{
public function chargeForOrder(OrderId $orderId, Money $amount): PaymentId;
public function refund(PaymentId $paymentId): void;
}
// Module: Inventory (public API only)
namespace App\Module\Inventory;
interface InventoryModuleApi
{
public function checkAvailability(array $items): AvailabilityResult;
public function reserve(ReservationRequest $request): ReservationId;
public function release(ReservationId $id): void;
}
// Modules communicate through interfaces, not direct DB access
// Each module has its own database schema (or separate tables)
// Migration to microservices: extract module -> deploy as service
Правила модульного монолита
| Правило | Описание |
|---|---|
| Публичный API | Модули общаются только через интерфейсы |
| Нет прямого доступа к БД | Один модуль не читает таблицы другого |
| Собственная схема | Каждый модуль владеет своими таблицами |
| Внутренние события | Асинхронная связь через внутренний event bus |
| Тестируемость | Модуль тестируется изолированно |
Модульный монолит -- часто лучший выбор. Он даёт чистую архитектуру без операционной сложности микросервисов. Многие компании успешно работают на модульном монолите с сотнями разработчиков (Shopify).
Микросервисы
Каждый сервис -- отдельный процесс со своей базой данных, деплоится и масштабируется независимо.
Преимущества
- Независимый деплой (команда A не ждёт команду B)
- Масштабирование конкретных сервисов
- Технологическая свобода (PHP для API, Go для processing)
- Изоляция отказов (падение одного сервиса не роняет все)
- Маленькие кодовые базы, быстрая сборка
Сложности
- Распределённые транзакции (saga вместо ACID)
- Сетевые вызовы вместо вызовов функций (задержка, ошибки)
- Eventual consistency
- Операционная сложность (мониторинг, трассировка, деплой)
- Дублирование данных
- Сложная отладка (distributed tracing)
<?php
declare(strict_types=1);
/**
* Microservice: independent deployment, own database
*/
final class OrderMicroservice
{
public function __construct(
private readonly \PDO $db, // Own database
private readonly PaymentClient $paymentClient, // HTTP client to Payment service
private readonly InventoryClient $inventoryClient, // HTTP client to Inventory service
private readonly KafkaProducer $eventBus, // Async communication
) {}
public function createOrder(array $request): array
{
// Step 1: Save order locally
$orderId = $this->saveOrder($request);
// Step 2: Publish event (async, eventually consistent)
$this->eventBus->send('orders', [
'type' => 'order.created',
'order_id' => $orderId,
'items' => $request['items'],
'total' => $request['total'],
]);
// Other services react to event:
// - Inventory service reserves items
// - Payment service initiates charge
// - Notification service sends email
return ['order_id' => $orderId, 'status' => 'pending'];
}
private function saveOrder(array $request): string
{
$id = uuid_create();
$stmt = $this->db->prepare(<<<SQL
INSERT INTO orders (id, user_id, total_amount, status)
VALUES (:id, :user_id, :total, 'pending')
SQL);
$stmt->execute([
'id' => $id,
'user_id' => $request['user_id'],
'total' => $request['total'],
]);
return $id;
}
}
Когда мигрировать с монолита
Сигналы для миграции
| Сигнал | Описание |
|---|---|
| Размер команды > 20 | Merge-конфликты, долгие PR reviews |
| Длинный цикл деплоя | > 1 часа от коммита до production |
| Масштабирование | Один компонент потребляет 90% ресурсов |
| Частые откаты | Изменение в одном модуле ломает другой |
| Технологические ограничения | Нужен другой язык/фреймворк для части системы |
НЕ мигрировать если
- Команда < 10 разработчиков
- Нет проблем с деплоем
- Нет проблем с масштабированием
- Домен не до конца понятен (boundaries will be wrong)
- Нет опыта эксплуатации распределённых систем
Стратегия миграции: Strangler Fig
Этап 1: Монолит Этап 2: Первый сервис
┌──────────────────┐ ┌──────────────────┐
│ Монолит │ │ ┌────────────┐ │
│ │ │ │ Монолит │ │
│ Orders │ ──> │ │ Orders │ │
│ Payments │ │ │ Payments │ │
│ Users │ │ └────────────┘ │
│ Notifications │ │ ┌──────────┐│
└──────────────────┘ │ │ Notif. ││
│ │ Service ││
│ └──────────┘│
└──────────────────┘
Этап 3: Ещё сервисы Этап 4: Готово
┌──────────────────┐ ┌─────┐ ┌─────┐
│ ┌────────────┐ │ │Order│ │Pay │
│ │ Монолит │ │ ──> │ Svc │ │ Svc │
│ │ Orders │ │ └─────┘ └─────┘
│ │ Payments │ │ ┌─────┐ ┌─────┐
│ └────────────┘ │ │User │ │Notif│
│ ┌─────┐ ┌─────┐ │ │ Svc │ │ Svc │
│ │Notif│ │User │ │ └─────┘ └─────┘
│ │ Svc │ │ Svc │ │
│ └─────┘ └─────┘ │
└──────────────────┘
Правило: начинайте с модульного монолита. Извлекайте микросервисы только когда есть конкретная причина (масштабирование, независимый деплой, другая технология).
Сравнение подходов
| Критерий | Монолит | Модульный монолит | Микросервисы |
|---|---|---|---|
| Сложность | Низкая | Средняя | Высокая |
| Независимый деплой | Нет | Нет | Да |
| Масштабирование | Всё вместе | Всё вместе | Независимое |
| Транзакции | ACID | ACID | Saga/eventual |
| Команда | 1-15 чел | 5-50 чел | 20+ чел |
| Отладка | Простая | Средняя | Сложная |
| Подходит для | Стартапы, MVP | Средний бизнес | Крупные системы |
Итоги
- Монолит -- отличный выбор для стартапов и небольших команд
- Модульный монолит даёт чистую архитектуру без распределённой сложности
- Микросервисы нужны при реальных проблемах масштабирования и деплоя
- Strangler Fig -- безопасная стратегия постепенной миграции
- Микросервисы -- не цель, а инструмент решения конкретных проблем