Как распределять функциональность между микросервисами на примере проведения заказа
Что проверяют
Умеете ли вы резать систему по бизнес-границам, а не по таблицам, и знаете ли, как провести заказ через несколько сервисов без распределённой транзакции. Следом спрашивают: что будет, если оплата прошла, а товара на складе уже нет.
Как отвечать
Начните с границ контекстов (bounded context из DDD), а не со списка сущностей:
- Ordering: жизненный цикл заказа и статусы. Хранит снимок цен и состава на момент покупки, а не ссылку на каталог;
- Catalog: товары и цены, отдаёт данные на чтение;
- Inventory: остатки и резервирование;
- Payment: платежи, возвраты, общение с платёжным провайдером;
- Delivery и Notification: доставка и уведомления.
У каждого сервиса своя база, в чужие таблицы никто не ходит.
Проведение заказа описывается сагой: создать заказ в статусе Pending, зарезервировать товар, списать деньги, подтвердить. На каждый шаг есть компенсация: снять резерв, вернуть платёж, отменить заказ. Оркестрация (координатор, в .NET это state machine saga в MassTransit или своя машина состояний) нагляднее для длинных процессов; хореография через события проще, но поток труднее отследить.
Обязательные механизмы:
- transactional outbox: событие пишется в той же транзакции, что и изменение заказа, а публикуется отдельным процессом. Иначе заказ сохранится, а событие потеряется;
- идемпотентность: брокер доставляет «как минимум один раз», поэтому потребители помнят обработанные
MessageId, а в платёж уходит ключ идемпотентности; - устойчивость синхронных вызовов: gRPC или HTTP с таймаутами и повторами через
Microsoft.Extensions.Http.Resilienceна базе Polly.
Где спотыкаются
Нарезают сервисы по CRUD-сущностям: User Service, Product Service. Получается распределённый монолит, где один заказ требует пяти синхронных вызовов.
Предлагают двухфазный коммит или общую базу на все сервисы. Это убивает независимость, ради которой всё затевалось.
Не продумывают компенсации и повторы: при сбое посередине остаются списанные деньги без заказа или резерв, который никто не снимет.