DDD-спецификация: гайд для инженера сопровождения
Сопровождение DDD: инциденты, зависшие Saga, SQL-диагностика.
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. Типичные обращения пользователей
"Не могу отменить заказ"
- Проверить текущий статус заказа:
SELECT status FROM orders WHERE id = ? - Если статус
CREATEDилиCONFIRMED-- отмена должна быть доступна. Проверить, что пользователь является владельцем заказа (customer_id). Проверить логи на наличие ошибок - Если статус
PAID,SHIPPEDилиDELIVERED-- объяснить пользователю, что после оплаты прямая отмена невозможна. Для возврата средств необходимо обратиться к администратору, который инициирует процесс возврата
"Заказ завис"
- Определить, на каком этапе завис заказ. Проверить статус заказа и статус Saga
- Если заказ в
CONFIRMEDи долго не переходит вPAID-- Saga обработки заказа могла зависнуть. Проверить таблицуsaga_state(см. SQL-запросы ниже) - Найти зависший шаг Saga, проверить логи на этом шаге. Причина обычно -- недоступность внешней системы (склад, оплата)
- При необходимости -- перезапустить шаг или эскалировать разработчикам
"Оплата не прошла"
- Проверить, было ли событие
OrderConfirmedопубликовано: посмотреть в таблицуdomain_events_outboxпоaggregate_id = orderIdиevent_type = 'OrderConfirmed' - Если событие не опубликовано (
published = false) -- проблема с Outbox-механизмом. Эскалировать DevOps - Если событие опубликовано -- проверить статус Saga. Шаг 2 ("СоздатьПлатеж") мог завершиться ошибкой
- Проверить статус платежа в платежном шлюзе (через администраторскую панель шлюза или API)
- Если шлюз подтверждает оплату, но событие
OrderPaidне получено -- проверить consumer lag в Kafka, наличие сообщений в DLQ
"Товар закончился после оформления"
- Проверить, есть ли событие
OrderCancelledдля этого заказа:SELECT * FROM domain_events_outbox WHERE aggregate_id = ? AND event_type = 'OrderCancelled' - Если заказ отменен -- проверить, был ли снят резерв на складе (событие
OrderCancelledдолжно было уйти в контекст "Склад") - Если заказ не отменен, но товара нет -- Saga обработки заказа на шаге 1 ("РезервироватьТовар") должна была получить ошибку и запустить компенсацию. Проверить
saga_state
"Не пришло уведомление"
- Проверить, было ли соответствующее событие опубликовано в Kafka (например,
OrderCreated,OrderShipped) - Проверить consumer lag группы уведомлений -- возможно, обработка отстает
- Проверить DLQ топик
order-events-dlq-- сообщение могло попасть туда из-за ошибки обработки - Если событие обработано, но уведомление не отправлено -- эскалировать команде, ответственной за контекст "Уведомления"
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
- Любые инциденты, связанные с аутентификацией и авторизацией