Easy📖Теория3 min

Обзор микросервисной архитектуры

Монолит vs микросервисы vs модульный монолит, когда мигрировать и ключевые trade-offs

Обзор микросервисной архитектуры

Эволюция архитектур

Монолит          Модульный монолит       Микросервисы
┌──────────┐    ┌──────────────────┐    ┌─────┐ ┌─────┐ ┌─────┐
│          │    │ ┌────┐ ┌────┐   │    │ 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 -- безопасная стратегия постепенной миграции
  • Микросервисы -- не цель, а инструмент решения конкретных проблем

Проверь себя

🧪

Какой паттерн позволяет постепенно мигрировать с монолита на микросервисы?

🧪

Что такое Distributed Monolith?

🧪

Когда НЕ стоит мигрировать на микросервисы?

🧪

Что отличает модульный монолит от обычного?

🧪

В каком случае монолит -- лучший выбор?