DDD-спецификация: гайд для инженера сопровождения

Сопровождение DDD: инциденты, зависшие Saga, SQL-диагностика.

DDD сопровождение

DDD сопровождение — инженер сопровождения работает с системой после запуска в production. Его задачи -- обработка инцидентов, решение обращений пользователей, диагностика зависших процессов и мониторинг здоровья системы.

Этот гайд написан на основе DDD-спецификации: Шаблон. Для вопросов инфраструктуры и деплоя -- см. Гайд DevOps.

Все примеры используют единый домен -- "Интернет-магазин: оформление заказа".


DDD сопровождение: 1. Понимание доменной модели

Для эффективного сопровождения необходимо понимать ключевые объекты системы и их смысл.

Order (Заказ) -- центральный объект. Содержит список позиций, адрес доставки, итоговую сумму и статус. Каждый заказ принадлежит конкретному покупателю (customerId).

OrderItem (Позиция заказа) -- конкретный товар в заказе с количеством и ценой. Цена фиксируется на момент оформления заказа (хранится как ProductSnapshot), поэтому изменение цены в каталоге не влияет на существующие заказы.

Статусы заказа и что они означают для пользователя:

СтатусЗначение для пользователяЧто можно делать
CREATEDЗаказ оформлен, ожидает подтвержденияДобавлять/удалять товары, отменять
CONFIRMEDПокупатель подтвердил заказОтменять (до оплаты)
PAIDОплата прошла успешноОжидать отправки. Отмена невозможна -- только возврат через администратора
SHIPPEDЗаказ передан в доставкуОжидать доставки. Есть трек-номер
DELIVEREDЗаказ доставленКонечное состояние
CANCELLEDЗаказ отмененКонечное состояние

2. Жизненный цикл заказа

Нормальный путь:

CREATED -> CONFIRMED -> PAID -> SHIPPED -> DELIVERED

Покупатель создает заказ, подтверждает его, система проводит оплату через платежный шлюз, менеджер отправляет заказ с трек-номером, курьер доставляет.

Отмена заказа. Возможна только в статусах CREATED и CONFIRMED (до оплаты). Покупатель может отменить свой заказ сам. При отмене подтвержденного заказа система автоматически снимает резерв на складе.

Возврат средств. Для заказов в статусах PAID, SHIPPED, DELIVERED прямая отмена невозможна. Администратор может инициировать возврат (InitiateRefund) -- это запускает отдельную Saga, которая возвращает деньги через платежный шлюз и (при необходимости) возвращает товар на склад. Статус заказа при этом не меняется -- возврат обрабатывается как отдельный процесс.


3. Типичные обращения пользователей

"Не могу отменить заказ"

  1. Проверить текущий статус заказа: SELECT status FROM orders WHERE id = ?
  2. Если статус CREATED или CONFIRMED -- отмена должна быть доступна. Проверить, что пользователь является владельцем заказа (customer_id). Проверить логи на наличие ошибок
  3. Если статус PAID, SHIPPED или DELIVERED -- объяснить пользователю, что после оплаты прямая отмена невозможна. Для возврата средств необходимо обратиться к администратору, который инициирует процесс возврата

"Заказ завис"

  1. Определить, на каком этапе завис заказ. Проверить статус заказа и статус Saga
  2. Если заказ в CONFIRMED и долго не переходит в PAID -- Saga обработки заказа могла зависнуть. Проверить таблицу saga_state (см. SQL-запросы ниже)
  3. Найти зависший шаг Saga, проверить логи на этом шаге. Причина обычно -- недоступность внешней системы (склад, оплата)
  4. При необходимости -- перезапустить шаг или эскалировать разработчикам

"Оплата не прошла"

  1. Проверить, было ли событие OrderConfirmed опубликовано: посмотреть в таблицу domain_events_outbox по aggregate_id = orderId и event_type = 'OrderConfirmed'
  2. Если событие не опубликовано (published = false) -- проблема с Outbox-механизмом. Эскалировать DevOps
  3. Если событие опубликовано -- проверить статус Saga. Шаг 2 ("СоздатьПлатеж") мог завершиться ошибкой
  4. Проверить статус платежа в платежном шлюзе (через администраторскую панель шлюза или API)
  5. Если шлюз подтверждает оплату, но событие OrderPaid не получено -- проверить consumer lag в Kafka, наличие сообщений в DLQ

"Товар закончился после оформления"

  1. Проверить, есть ли событие OrderCancelled для этого заказа: SELECT * FROM domain_events_outbox WHERE aggregate_id = ? AND event_type = 'OrderCancelled'
  2. Если заказ отменен -- проверить, был ли снят резерв на складе (событие OrderCancelled должно было уйти в контекст "Склад")
  3. Если заказ не отменен, но товара нет -- Saga обработки заказа на шаге 1 ("РезервироватьТовар") должна была получить ошибку и запустить компенсацию. Проверить saga_state

"Не пришло уведомление"

  1. Проверить, было ли соответствующее событие опубликовано в Kafka (например, OrderCreated, OrderShipped)
  2. Проверить consumer lag группы уведомлений -- возможно, обработка отстает
  3. Проверить DLQ топик order-events-dlq -- сообщение могло попасть туда из-за ошибки обработки
  4. Если событие обработано, но уведомление не отправлено -- эскалировать команде, ответственной за контекст "Уведомления"

4. Работа с Saga

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

  • Saga 1: Обработка заказа (триггер: OrderConfirmed) -- резервирование товара, оплата, создание заявки на доставку
  • Saga 2: Возврат средств (триггер: RefundInitiated) -- возврат денег, возврат товара на склад

Как найти зависшую Saga. Saga считается зависшей, если она в статусе RUNNING более 10 минут (см. SQL ниже). Поле current_step покажет, на каком шаге остановился процесс.

Как определить причину. Посмотреть поле payload в saga_state -- там хранится контекст выполнения. Проверить логи по sagaId или orderId. Обычно причина -- таймаут или ошибка внешней системы.

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

Ручная компенсация. Если Saga не может завершиться автоматически (например, платежный шлюз вернул деньги, но статус не обновился), компенсацию выполняют вручную: обновляют состояние в БД и уведомляют пользователя. Такие действия обязательно документируются.


5. Мониторинг для сопровождения

Ключевые дашборды:

  • API latency (p95 должен быть <= 200ms) -- деградация производительности влияет на пользователей
  • Error rate (5xx ошибки) -- если > 1%, система нестабильна
  • Kafka consumer lag -- если > 1000 сообщений, события обрабатываются с задержкой
  • Зависшие Saga -- количество Saga в статусе RUNNING > 10 минут
  • Circuit breaker status -- если OPEN, внешняя система недоступна

Критичные алерты, требующие немедленной реакции:

  • Error rate > 1% -- возможен массовый сбой
  • Все circuit breaker в состоянии OPEN -- внешняя система полностью недоступна
  • Pod restarts > 3 за 10 минут -- приложение постоянно падает
  • Disk usage > 80% -- скоро закончится место
  • PostgreSQL connections > 80% пула -- приложение может перестать обрабатывать запросы

6. Каталог ошибок как справочник

Каждый код ошибки из каталога имеет конкретный смысл и рекомендуемое действие:

Код ошибкиЧто случилосьЧто делать
ORDER_EMPTYПопытка создать заказ без товаровОбъяснить пользователю: нужно добавить хотя бы один товар
ORDER_ITEM_INVALID_QUANTITYКоличество товара за пределами 1--99Попросить указать корректное количество
ORDER_TOTAL_TOO_LOWСумма заказа менее 1 рубляДобавить товары в заказ
ORDER_CANCEL_FORBIDDENПопытка отменить оплаченный заказНаправить к администратору для оформления возврата
ORDER_MODIFICATION_FORBIDDENПопытка изменить подтвержденный заказОбъяснить: изменения возможны только до подтверждения
ORDER_ITEM_OUT_OF_STOCKТовар закончился на складеПредложить убрать товар или уменьшить количество
ORDER_NOT_FOUNDЗаказ не существует или принадлежит другому пользователюПроверить ID заказа. Не раскрывать информацию о чужих заказах
CATALOG_SERVICE_UNAVAILABLEСервис каталога не отвечаетПовторить через 1--2 минуты. При длительной недоступности -- эскалировать
PAYMENT_SERVICE_UNAVAILABLEПлатежный шлюз не отвечаетПовторить через 1--2 минуты. Проверить статус шлюза
WAREHOUSE_SERVICE_UNAVAILABLEСервис склада не отвечаетПовторить через 1--2 минуты. Проверить circuit breaker

7. Интеграции: проверка доступности внешних систем

Система интегрирована с четырьмя внешними контекстами. Каждый может стать источником инцидента.

Health check. Эндпоинт /actuator/health проверяет доступность PostgreSQL, Redis, Kafka и внешних сервисов. Если health check возвращает статус DOWN -- система неработоспособна.

При недоступности каталога товаров. Система использует кэш (Redis, TTL 30 минут). Если кэш протух -- ошибка CATALOG_SERVICE_UNAVAILABLE. Пользователи не смогут создавать новые заказы, но существующие заказы продолжат обрабатываться.

При недоступности платежного шлюза. Saga обработки заказа на шаге "СоздатьПлатеж" выполнит до 3 retry с exponential backoff. При исчерпании попыток -- компенсация (отмена резерва на складе) и отмена заказа. Пользователь получит уведомление.

При недоступности склада. Saga на шаге "РезервироватьТовар" выполнит retry. При неудаче -- заказ отменяется, пользователь уведомляется о том, что товар недоступен.

При недоступности доставки. Заявка на доставку создается асинхронно через Kafka. При ошибке -- логирование, заявку можно создать позже вручную. Заказ при этом не отменяется.


8. SQL-запросы для диагностики

Найти заказ по ID

SELECT * FROM orders WHERE id = ?;

Получить позиции заказа

SELECT * FROM order_items WHERE order_id = ?;

Найти зависшие Saga

SELECT * FROM saga_state
WHERE status = 'RUNNING'
  AND started_at < NOW() - INTERVAL '10 minutes';

Найти неопубликованные события (проблемы с Outbox)

SELECT * FROM domain_events_outbox
WHERE published = false
  AND created_at < NOW() - INTERVAL '5 minutes';

Статистика заказов по статусам

SELECT status, COUNT(*) FROM orders GROUP BY status;

Найти заказы конкретного пользователя

SELECT id, status, total_amount, created_at
FROM orders
WHERE customer_id = ?
ORDER BY created_at DESC;

Найти события для конкретного заказа

SELECT event_type, payload, created_at, published
FROM domain_events_outbox
WHERE aggregate_id = ?
ORDER BY created_at;

9. Эскалация

Не все проблемы решаются на уровне сопровождения. Важно своевременно передать инцидент нужной команде.

Эскалация разработчикам:

  • Ошибки в бизнес-логике (неправильный расчет суммы, некорректный переход статуса)
  • Зависшие Saga, которые не удается перезапустить стандартным способом
  • Неожиданные 500 ошибки без очевидной причины
  • Несоответствие данных между таблицами (нарушение целостности)

Эскалация DevOps:

  • Недоступность инфраструктуры (PostgreSQL, Redis, Kafka)
  • Проблемы с производительностью (p95 > 200ms)
  • Disk usage > 80%
  • Pod restarts, OOM kills
  • Проблемы с Outbox-механизмом (неопубликованные события накапливаются)
  • Consumer lag в Kafka растет и не уменьшается

Эскалация Security:

  • Подозрительная активность: массовые запросы к чужим заказам (потенциальный IDOR)
  • Необычные паттерны rate limit: один пользователь исчерпывает лимиты регулярно
  • Обращения, связанные с утечкой персональных данных
  • Запросы на удаление персональных данных (GDPR / 152-ФЗ) -- передать для выполнения crypto-shredding
  • Любые инциденты, связанные с аутентификацией и авторизацией