Anti corruption layer
Часто в наших приложениях мы обращаемся к каким-то внешним системам. Иногда мы просто обращаемся к ним, иногда ждем обратных вызовов. Это может быть платежная система, сервис уведомлений, инвентаризации или что-то ещё.
Ключевое в таких интеграциях то, что такие системы имеют свой жизненный цикл, свой контекст и часто их устройство слабо похоже на то, что нам надо от них, однако интеграция с ними может быть жизненно необходима для нашего приложения.
Для того, чтобы сделать наше приложение более устойчивым к изменениям внешнего API и одновременно сохранить нашу логику чистой, мы выделяем отдельный компонент - Anti Corruption Layer. Его задача - скрыть за собой детали взаимодействия с этой системой, предоставив нашей бизнес логике понятное ей API.
Структура с использованием ACL может состоять из таких частей:
• Какая-то часть нашей бизнес-логики использует интеграцию. Она знает о том, что принципиально это за сервис, однако не знает о деталях общения с ним.
• У бизнес-логики есть требования к взаимодействию. Они выражаются в виде интерфейсов и каких-то моделей данных. Хоть мы и абстрагируемся от деталей, мы всё-таки выражаем наше представление о конкретном сервисе, но лишь об интересных нам аспектах и о данных в терминах понятных нашему бизнесу.
• Сторонний сервис для нас представляет некоторое API, удобное или не очень. Иногда это вызовы библиотек, иногда - удаленные обращения по HTTP или другим протоколам. Мы не контролируем код, который это реализует. Это чужой код.
• Если сторонний сервис не предоставляет для нас достаточно понятного API, мы самостоятельно реализуем клиентский фасад. Он обязательно выражается в терминах чужой системы, что позволит нам при её изменениях проще его обновлять. Он может реализовывать только часть вызовов, или игнорировать часть данных, но он достаточно примитивный (хоть и может содержать сложные парсеры DTO) и призван просто улучшить читаемость чужого API. Это можно сделать вводя более строгие типы, конкретизируя сигнатуры методов, разделяя вызовы или группируя по смыслу. Важно не пытаться тут сразу транслировать удаленные вызовы в наши доменные сущности. Если у нас уже есть достаточно хорошая реализация чужого API, то дополнительный фасад не требуется.
• Чтобы совместить фасад с нашим интерфейсом, мы делаем адаптер. Он будет трансформировать вызовы выраженные в наших терминах (понятные нашей БЛ) в вызовы в терминах чужого сервиса (понятные фасаду или чужому коду). При этом на одно обращение к нему, он может делать несколько вызовов чужого API, если это требуется.
• Так как кроме самих вызовов необходимо так же менять форму данных, это часто удобно вынести в отдельные трансляторы. В качестве них могут быть использованы дополнительные объекты или просто методы адаптера.
Пример из практики в комментарии
Дополнительные материалы:
• https://martinfowler.com/articles/patterns-legacy-displacement/legacy-mimic.html
• https://adaptix.readthedocs.io/en/latest/conversion/tutorial.html
Post #70
20K

- 👍 66
- ❤ 10
- 🤡 9
- 🤔 6
- 🥱 4
- 🐳 4
- 👾 2