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

Как микросервисы общаются с базой данных?

2.2 Middle🔥 171 комментариев
#Микросервисы и архитектура

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

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

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

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

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

Ключевые принципы доступа к данным

  1. Private Database per Service (Приватная база на сервис): Каждый микросервис владеет своей схемой данных и БД. Прямой доступ из других сервисов запрещён. Это обеспечивает слабую связанность (loose coupling) и инкапсуляцию данных.
  2. API как контракт: Единственный способ получить или изменить данные — через публичный API сервиса (обычно REST/gRPC). Сервис выступает "хранителем" своих данных.
  3. Выбор технологии (Polyglot Persistence): Каждый сервис может использовать тип БД, оптимальный для его задач: реляционную (PostgreSQL), документную (MongoDB), ключ-значение (Redis), графовую и т.д.

Основные паттерны взаимодействия

1. Прямой доступ к собственной БД (Шаблон "База данных на сервис")

Самый распространённый подход. Сервис работает исключительно со своей приватной БД.

// Пример: Сервис заказов (OrderService) работает со своей PostgreSQL
package orderrepository

import (
    "context"
    "github.com/jmoiron/sqlx"
)

type PostgresRepository struct {
    db *sqlx.DB
}

func (r *PostgresRepository) CreateOrder(ctx context.Context, order *Order) error {
    query := `INSERT INTO orders (user_id, total, status) VALUES ($1, $2, $3) RETURNING id`
    return r.db.QueryRowContext(ctx, query, order.UserID, order.Total, order.Status).Scan(&order.ID)
}

func (r *PostgresRepository) GetByID(ctx context.Context, id int64) (*Order, error) {
    var order Order
    err := r.db.GetContext(ctx, &order, "SELECT * FROM orders WHERE id = $1", id)
    return &order, err
}

2. Компенсирующие транзакции (Saga Pattern)

Для бизнес-транзакций, затрагивающих несколько сервисов, используют Saga — последовательность локальных транзакций с компенсациями.

// Упрощённый пример координации через события
func (s *OrderSagaService) CreateOrderSaga(ctx context.Context, orderReq OrderRequest) error {
    // 1. Локальная транзакция: создать заказ в статусе "PENDING"
    order, err := s.orderRepo.CreatePendingOrder(ctx, orderReq)
    if err != nil {
        return err
    }

    // 2. Отправить событие "OrderCreated" для списания средств с баланса
    if err := s.eventBus.Publish(OrderCreatedEvent{OrderID: order.ID, Amount: order.Total}); err != nil {
        s.orderRepo.CompensateOrder(ctx, order.ID) // Компенсация: отменить заказ
        return err
    }

    // 3. Дождаться события "PaymentApproved" и обновить статус заказа
    // Если придет "PaymentFailed" — запустить компенсирующую логику
    return nil
}

3. Командный раздел ответственности за запросы (CQRS)

Для сложных запросов, требующих данных из нескольких сервисов, используют CQRS (Command Query Responsibility Segregation). Часто в паре с ним применяется Event Sourcing.

  • Команды (Commands) изменяют данные через API сервисов.
  • Запросы (Queries) читают данные из специально подготовленного read-хранилища (денерированная view, материализованное представление), которое обновляется асинхронно через события.
// Read-модель обновляется слушателем событий
func (h *OrderHistoryProjection) HandlePaymentApproved(event PaymentApprovedEvent) error {
    // Обновить данные в оптимизированной для чтения БД (например, MongoDB или Denormalized SQL table)
    return h.readDB.UpdateOrderStatus(event.OrderID, "PAID", event.ApprovedAt)
}

// Запрос использует только read-модель
func (s *OrderQueryService) GetUserOrders(ctx context.Context, userID string) ([]OrderView, error) {
    var orders []OrderView
    // Быстрый запрос к денормализованной коллекции, без join'ов
    err := s.readDB.Find(&orders, bson.M{"user_id": userID}).Sort("-created_at").All(ctx)
    return orders, err
}

4. Асинхронная репликация данных

Для повышения отказоустойчивости и производительности данных могут реплицироваться между инстансами БД одного сервиса или в read-хранилище CQRS.

Проблемы и решения

  • Согласованность данных (Consistency): Вместо ACID-транзакций между сервисами используют итоговую согласованность (Eventual Consistency) через события и компенсации.
  • Сложность запросов: Join'ы между таблицами разных сервисов невозможны. Решения:
    *   **API Composition**: Сервис-агрегатор последовательно запрашивает данные у нескольких сервисов через их API.
    *   **CQRS**: Создание денормализованной read-модели, подготовленной под конкретные запросы.
  • Управление миграциями: Каждый сервис самостоятельно управляет миграциями своей БД с помощью инструментов типа Flyway или Goose.
# Пример запуска миграций для Go-сервиса
goose -dir ./migrations postgres "user=order_service dbname=orders sslmode=disable" up

Техническая реализация в Go

  1. Слой доступа к данным (Repository): Изолирует логику работы с БД.
  2. Миграции: Скрипты на SQL или библиотеки в коде (например, github.com/golang-migrate/migrate).
  3. Пул соединений: Использование sqlx или pgx с настройкой пула.
  4. Контекст (Context): Все операции с БД должны принимать context.Context для управления таймаутами и отменой.
  5. Транзакции: Локальные транзакции внутри сервиса для согласованности.
// Пример использования транзакции и контекста
func (s *Service) UpdateOrderWithItems(ctx context.Context, orderID int64, items []Item) error {
    tx, err := s.db.BeginTxx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx.Rollback()

    if err := s.updateOrderTotal(ctx, tx, orderID, items); err != nil {
        return err
    }
    if err := s.insertOrderItems(ctx, tx, orderID, items); err != nil {
        return err
    }

    return tx.Commit()
}

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

Как микросервисы общаются с базой данных? | PrepBro