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

Ответ

Архитектура следовала принципам предметно-ориентированного проектирования (DDD), обеспечивая слабую связность и высокую степень автономности сервисов.

Основные характеристики:

  • Декомпозиция по доменам: Сервисы соответствовали бизнес-доменам (user-management, order-processing, payment-service).
  • Автономность: Каждый сервис владел своей схемой БД (использовались PostgreSQL, MongoDB) и развертывался независимо.
  • Стили коммуникации:
    • Синхронная: REST API и gRPC для прямых запросов-ответов.
    • Асинхронная: Брокер сообщений RabbitMQ (позже Kafka) для событий (user.registered, order.placed), обеспечивая слабую связность.
  • Инфраструктура: Оркестрация через Kubernetes с Helm-чартами, service mesh (Istio) для управления трафиком и безопасности.
  • Наблюдаемость: Единый стек: Prometheus (метрики), Grafana (дашборды), Jaeger (трассировка), Loki (логи).

Пример автономного теста сервиса (pytest):

# Тест изолирован от других сервисов благодаря мокам
def test_create_user_unit(user_service, mock_db_session, mocker):
    """Тест бизнес-логики создания пользователя."""
    # Мок внешней зависимости (например, сервиса нотификаций)
    mock_notify = mocker.patch("services.notification_client.send")

    new_user_data = {"email": "test@example.com", "name": "Test"}
    result = user_service.create_user(new_user_data)

    assert result.id is not None
    assert result.email == new_user_data["email"]
    # Проверка, что побочный эффект (нотификация) был вызван
    mock_notify.assert_called_once()

Сложности и решения: Для обеспечения согласованности данных в распределенных транзакциях применялся Паттерн Сага (Saga Pattern). Отладка упрощалась за счет сквозных идентификаторов запросов (correlation IDs) в логах и трассировках.

Ответ 18+ 🔞

А, слушай, вот это архитектура была — просто песня, если честно. Не то что эти ваши монолиты, где всё в одной куче, как говно в проруби. Мы тут забили на эту срань и пошли по пути предметно-ориентированного проектирования, это DDD, если по-умному. Суть в чём? Чтобы всё было слабо связано, а сервисы жили своей жизнью, как соседи по коммуналке — вроде рядом, но каждый сам по себе и в душу друг другу не лезет.

Что у нас там было по полочкам:

  • Декомпозиция по доменам: Каждый сервис — это кусок бизнеса. user-management — там, юзеров плодить, order-processing — заказы мутить, payment-service — бабки стричь. Чётко, ясно, без этой ебалы «а чьё это вообще?».
  • Автономность полная: Каждый сервис — царь и бог своей базы данных. У кого PostgreSQL, у кого MongoDB. Разворачивать можно по одному, не надо всю эту махину, как утюг, поднимать.
  • Как они общались:
    • Синхронно: Когда срочно надо — REST API или gRPC (это такая жёсткая штука, быстрее и чётче).
    • Асинхронно: А когда можно не торопиться — через брокер сообщений. Сначала RabbitMQ, потом на Kafka переползли. Кидаешь событие типа user.registered или order.placed и спишь спокойно. Кто надо — тот подхватит. Связность — ниже плинтуса, красота.
  • Инфраструктура: Всё на Kubernetes болталось, управлялось Helm-чартами. А для тонких штук вроде маршрутизации или безопасности — Istio (service mesh, если кому интересно).
  • Наблюдаемость: Чтобы не гадать, что там внутри происходит, был полный стек: Prometheus метрики собирал, Grafana красивые графики рисовала, Jaeger за трассировкой следил, а Loki логи в кучу собирал. В общем, видно всё, как на ладони, а не как в тумане.

Вот, глянь, как автономно тесты писались (pytest):

# Тест изолирован от других сервисов благодаря мокам
def test_create_user_unit(user_service, mock_db_session, mocker):
    """Тест бизнес-логики создания пользователя."""
    # Мок внешней зависимости (например, сервиса нотификаций)
    mock_notify = mocker.patch("services.notification_client.send")

    new_user_data = {"email": "test@example.com", "name": "Test"}
    result = user_service.create_user(new_user_data)

    assert result.id is not None
    assert result.email == new_user_data["email"]
    # Проверка, что побочный эффект (нотификация) был вызван
    mock_notify.assert_called_once()

Видишь? Сервис в вакууме. Никаких реальных вызовов куда-то на сторону. Всё замокано. Быстро, надёжно, не зависит ни от кого.

Сложности и решения: Ну а куда без них, блядь. Самая главная головная боль — как в этой раздолбанной системе данные согласовывать. Ты в одном сервисе обновил, а в другом — хуй. Для этого Паттерн Сага (Saga Pattern) применяли. Это когда длинная транзакция разбивается на цепочку маленьких, и если где-то пиздец, то остальные шаги откатываются. А чтобы не сойти с ума при отладке, везде прокидывали сквозные идентификаторы запросов (correlation IDs). Откроешь Jaeger или Loki, вбиваешь этот ID — и тебе вся история, как по ниточке, от начала и до конца, даже если запрос через десять сервисов пролетел. Без этого — просто тьма египетская, волнение ебать.