Какой основной принцип работы с базами данных в микросервисной архитектуре?

Ответ

Ключевой принцип — одна база данных на сервис (Database per Service). Каждый микросервис владеет своими данными и имеет эксклюзивный доступ к своей базе данных.

Это делается для обеспечения слабой связанности (loose coupling) между сервисами. Ни один сервис не может напрямую обращаться к базе данных другого сервиса; взаимодействие происходит только через опубликованные API.

Преимущества такого подхода:

  • Автономность: Команды могут изменять схему БД, обновлять и масштабировать её, не затрагивая другие сервисы.
  • Полиглотная персистентность (Polyglot Persistence): Возможность выбрать лучшую технологию хранения для каждого сервиса. Например:
    • Сервис заказов — реляционная СУБД (PostgreSQL) для ACID-транзакций.
    • Сервис каталога товаров — документо-ориентированная СУБД (MongoDB) для гибкой структуры.
    • Сервис рекомендаций — графовая СУБД (Neo4j).
  • Изоляция сбоев: Проблемы с базой данных одного сервиса не влияют напрямую на работоспособность других.

Основные вызовы и их решения:

  1. Согласованность данных (Data Consistency)

    • Проблема: Атомарные транзакции (ACID) не могут охватывать несколько баз данных.
    • Решение: Паттерн Saga. Бизнес-транзакция разбивается на серию локальных транзакций в каждом сервисе. Для координации используются брокеры сообщений (Kafka, RabbitMQ), что приводит к конечной согласованности (eventual consistency).
  2. Запросы к данным из нескольких сервисов

    • Проблема: Как получить данные, которые хранятся в базах данных разных сервисов (например, собрать информацию о заказе и пользователе)?
    • Решение: Паттерн API Composition. Создается сервис-агрегатор, который опрашивает другие сервисы по их API и объединяет результаты.

Ответ 18+ 🔞

Ага, вот смотри, тут есть один железобетонный принцип, который все эти умники в микросервисах вывели, — одна база данных на сервис. Каждый сервис — это как отдельная квартира со своим сейфом. Только он имеет к нему ключи. Никакой сосед не может вломиться к тебе в базу и навести там свой порядок, общаться можно только через дверной глазок, то есть через опубликованные API.

Ну и зачем это всё, спросишь? А вот зачем, ёпта:

  • Полная автономность, блядь. Хочешь переставить мебель в своей базе (схему поменять) или стену снести — да пожалуйста! Делай что хочешь, пока свой API не ломаешь. Никто из соседей даже не чихнёт.
  • Полиглотная персистентность, или "каждому овощу своя банка". Не надо всех пихать в одну PostgreSQL, как в общагу. Сервису заказов — пусть будет его ACID-транзакционный PostgreSQL. Сервису каталога, где товары как попало меняются — гибкий MongoDB. А для сервиса рекомендаций, где связи важны — Neo4j, графовая штука. Выбирай что хочешь, главное — в своей квартире.
  • Изоляция сбоев. Если у соседа сверху (сервиса оплат) база данных легла и горит, то твой сервис доставки пиццы не должен из-за этого тоже вставать колом. В идеале, конечно.

Но, как водится, за всё хорошее надо платить. И вот тут начинается самое интересное, пиздец.

Вызов первый: Согласованность данных. Раньше в монолите была одна большая транзакция на всё: списали деньги, создали заказ, резервировали товар — и если что-то пошло не так, откатили ВСЁ, как будто ничего и не было. Красота! А теперь у нас каждый сервис в своей квартире, со своим сейфом. Как сделать так, чтобы операция "оформить заказ" прошла согласованно по всем фронтам? Решение — паттерн Saga. Это когда наша большая транзакция разбивается на кучу маленьких локальных шагов. Сначала сервис заказов создаёт заказ "в ожидании оплаты", потом кидает событие "заказ создан". Сервис оплат его ловит, списывает бабки и кидает "оплата прошла". Сервис склада ловит это и резервирует товар. Если на каком-то шаге пиздец (денег нет), запускается компенсирующая транзакция — отмена резерва, возврат денег, отмена заказа. Всё это через брокеры сообщений вроде Kafka крутится. Итог — конечная согласованность. То есть через какое-то время всё устаканится, но не мгновенно. Привыкай.

Вызов второй: Запросы к данным из нескольких сервисов. Вот реальная задача: показать пользователю его заказ. А данные-то разбросаны: сам заказ — в сервисе заказов, а имя и адрес пользователя — в сервисе пользователей. Как собрать эту мозаику, если нельзя просто так взять и сделать JOIN между двумя разными базами? Решение — паттерн API Composition. Создаётся отдельный сервис-агрегатор (или шлюз), который выступает таким настырным менеджером. Он лезет в сервис заказов: "Дай заказ №123". Потом лезет в сервис пользователей: "А для заказа №123 дай данные юзера". Потом склеивает это всё в один красивый JSON и отдаёт на фронтенд. Просто? Да. Эффективно? Не всегда, особенно если сервисов много, но для простых случаев — то, что надо.

Вот так, блядь, и живём. Вроде и свободы больше, и гибкости, а головной боли — овердохуища. Но назад в монолит уже не хочется, там скучно.