Сервис доставки получает событие заказа от сервиса продаж, но одинаковое поле status имеет разные бизнес-смыслы:
{"orderId":"42","status":"READY"}
Какой механизм границы интеграции не позволит внешней модели напрямую проникнуть во внутреннюю модель сервиса доставки?
Используйте антикоррупционный слой — адаптер, который переводит внешний контракт и его значения во внутреннюю модель сервиса доставки. Он изолирует домен от чужих терминов, состояний и правил преобразования.
Прямое присваивание внешнего status внутреннему полю опасно: одинаковое слово может обозначать разные состояния, а изменение внешнего контракта начнёт менять внутреннюю бизнес-логику.
Антикоррупционный слой появился как практический паттерн интеграции независимых моделей в Domain-Driven Design. В распределённых системах разные команды моделируют один и тот же предмет с собственными терминами, инвариантами и жизненными циклами.
Без специальной границы интеграции модель одного контекста постепенно «заражает» другой. В результате сервисы теряют автономность, а изменение одного домена требует правок в чужом коде.
Сервис продаж может считать READY состоянием «заказ оплачен и готов к передаче в доставку». Сервис доставки может использовать READY только для означения «посылка собрана и ожидает курьера».
Если передать значение напрямую, доставка может преждевременно начать обработку заказа или, наоборот, не распознать допустимое состояние. Риск особенно высок, когда внешнее событие считается техническим DTO и используется внутри домена без проверки смысла.
Антикоррупционный слой принимает внешний контракт, валидирует его и преобразует в внутреннюю команду или внутреннее событие. Внутри сервиса доставки используются его собственные типы и термины, а не названия из сервиса продаж.
Такое преобразование не обязано быть простым переименованием. Адаптер может учитывать таблицу соответствий, контекст события, версию контракта и невозможные переходы. Если внешнее состояние не имеет безопасного аналога, сообщение следует отклонить или направить в отдельный поток ручной обработки, а не выбирать значение «примерно подходящее».
Граница должна находиться на входе в сервис: после преобразования доменные объекты не должны зависеть от внешнего JSON, названий полей или идентификаторов. Для исходящих сообщений применяется обратное преобразование — внутреннее состояние публикуется в отдельном внешнем контракте.
Компромисс — дополнительный код, тесты и необходимость поддерживать таблицу соответствий. Это оправдано, когда модели действительно принадлежат разным bounded context; если сервисы имеют одного владельца и одну модель, чрезмерный перевод может усложнить систему без архитектурной пользы.
Сервис продаж публиковал READY, CANCELLED и RETURNED. Сервис доставки напрямую сохранял эти значения в собственной таблице, хотя его жизненный цикл включал состояния AWAITING_PICKUP, IN_TRANSIT и DELIVERED.
Рассматривались два варианта. Первый — использовать общую библиотеку перечислений: это уменьшало дублирование, но связывало релизы и навязывало одну модель двум доменам. Второй — принимать внешний статус напрямую: решение было простым, но скрывало семантическое несовпадение и допускало некорректные переходы.
Выбрали антикоррупционный слой с явной таблицей преобразований и контрактными тестами на входные события. Для READY создавалась команда CREATE_SHIPMENT, а неподдерживаемые комбинации попадали в очередь ошибок. В результате изменение статусов в продажах не меняло внутренний автомат доставки, а спорные соответствия обнаруживались до обработки бизнес-операции.
Обычный маппинг переносит структуру данных между техническими объектами. Антикоррупционный слой переводит смысл: проверяет допустимость состояния, применяет правила контекста и не позволяет внешней модели стать частью внутренней доменной модели.
Преобразование внешнего контракта во внутреннюю модель обычно находится у получателя, потому что именно он владеет смыслом своей модели и решает, какие внешние данные допустимы. Отправитель может документировать и стабилизировать свой контракт, но не должен диктовать внутренние состояния другого сервиса.
Нельзя выбирать вариант только по техническому удобству. Нужно получить дополнительные данные, изменить контракт или принять решение явно: например, преобразовать событие в команду с последующим запросом недостающего контекста, если такая зависимость допустима. Если однозначное решение невозможно, сообщение следует отклонить или поместить в поток исключений с наблюдаемой причиной.