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

Должен ли каждый микросервис обладать своей БД?

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

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Обязательна ли собственная БД для каждого микросервиса?

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

Принцип владения данными микросервиса

Фундаментальное правило микросервисной архитектуры гласит: каждый микросервис должен владеть и управлять своей собственной моделью данных (зачастую — отдельной базой данных). Это прямое следствие принципа высокой связности и низкой связанности (high cohesion, loose coupling).

// Пример в C#: OrderService владеет своей БД заказов
public class OrderService
{
    private readonly OrderDbContext _dbContext; // Собственный контекст БД

    public async Task<Order> CreateOrderAsync(OrderRequest request)
    {
        var order = new Order { /* ... */ };
        await _dbContext.Orders.AddAsync(order);
        await _dbContext.SaveChangesAsync(); // Операция только в своей БД
        return order;
    }
}

// CustomerService владеет отдельной БД клиентов
public class CustomerService
{
    private readonly CustomerDbContext _dbContext; // Другой контекст БД
}

Почему собственная БД — это золотой стандарт?

1. Независимость и автономность (Autonomy)

  • Независимые развертывания: Можно обновить схему БД сервиса заказов, не затрагивая сервис каталога товаров.
  • Выбор оптимальной технологии (Polyglot Persistence): Сервис для аналитики может использовать ClickHouse, сервис корзины — Redis, а основной сервис заказов — SQL Server.
  • Масштабирование: БД каждого сервиса масштабируется отдельно в зависимости от нагрузки.

2. Изоляция сбоев (Fault Isolation)

  • Проблема в БД одного сервиса (например, блокировка таблиц) не "положит" всю систему. Остальные сервисы продолжат работу.

3. Строгие границы контекстов (Bounded Context)

Концепция из DDD: каждый сервис инкапсулирует свою доменную логику и данные. Заказ (Order) в сервисе доставки и Заказ в сервисе оплаты — это разные сущности с разными атрибутами и жизненным циклом.

Когда можно или нужно отступить от этого правила?

Полное следование принципу "один сервис — одна БД" сложно и дорого. На практике встречаются разумные компромиссы:

1. Разделение БД на логическом уровне

  • Один физический инстанс СУБД (например, PostgreSQL), но разные схемы (schemas) или базы (databases) для каждого сервиса.
  • Это дешевле в эксплуатации, но сохраняет логическую изоляцию. Риск — общая точка отказа (сервер БД).
-- Логическое разделение в одной СУБД
CREATE SCHEMA orders_service;
CREATE TABLE orders_service.orders (...);

CREATE SCHEMA catalog_service;
CREATE TABLE catalog_service.products (...);

2. Сервис-агрегатор для отчетности (CQRS Pattern)

  • Для сложных отчетов, требующих данных из нескольких сервисов, создается отдельный читающий сервис.
  • Он подписывается на события от других сервисов и поддерживает свою денаormalлизованную БД (read model), оптимизированную под запросы.

3. Начальная стадия проекта (Startup Phase)

  • На ранних этапах можно начать с модульного монолита (Modular Monolith), где модули разделены логически, но используют общую БД.
  • Это упрощает разработку, пока границы сервисов не ясны. Позже, при росте, можно физически разделить БД.

Абсолютные антипаттерны

  1. Прямые JOIN между таблицами, принадлежащими разным сервисам. Это создает сильную связность на уровне данных.
  2. Общие таблицы, доступные для записи нескольким сервисам. Это гарантирует проблемы с согласованностью данных и блокировками.
  3. Скрытое использование БД другого сервиса в обход его API.

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

Вместо общего доступа к БД сервисы должны общаться через:

  • Синхронные API (REST, gRPC): Для операций, требующих немедленного ответа.
  • Асинхронную коммуникацию через события: Используя брокеры сообщений (RabbitMQ, Apache Kafka). Это обеспечивает согласованность в конечном счете (Eventual Consistency).
// Пример асинхронного взаимодействия через события
public class OrderService
{
    private readonly IPublishEndpoint _publishEndpoint;

    public async Task PlaceOrderAsync(Order order)
    {
        // 1. Сохраняем заказ в своей БД
        await _orderRepository.AddAsync(order);

        // 2. Публикуем событие для всей системы
        await _publishEndpoint.Publish(new OrderPlacedEvent
        {
            OrderId = order.Id,
            CustomerId = order.CustomerId,
            TotalAmount = order.TotalAmount
        });
    }
}

// Сервис оплаты подписывается на это событие и обрабатывает его
public class PaymentServiceConsumer : IConsumer<OrderPlacedEvent>
{
    public async Task Consume(ConsumeContext<OrderPlacedEvent> context)
    {
        // Обработка платежа, используя только свои данные и полученное событие
    }
}

Итог и рекомендации

Стремитесь к модели "один микросервис — одна логическая база данных". Это краеугольный камень независимости сервисов. В production-системах для критичных сервисов предпочтительно физическое разделение. Однако, на старте или для некритичных, тесно связанных сервисов допустимо логическое разделение в рамках одной СУБД с четко прописанными границами.

Критерий принятия решения: Если вы можете изменить или заменить БД одного сервиса, не меняя код и не останавливая другие сервисы — вы на правильном пути. Если изменение схемы в одной таблице требует координации с тремя командами — это сигнал о нарушении границ владения данными.

Должен ли каждый микросервис обладать своей БД? | PrepBro