Интеграция перестаёт быть технической деталью, когда её поля, коды ошибок и правила доступа появляются в контроллерах, сервисах уровня приложения, моделях и бизнес-процессах. В этот момент смена поставщика, обновление API или устранение внешнего инцидента требуют изменять части приложения, которые не должны знать об этой системе.
Изолировать внешние интеграции в PHP означает установить чёткую границу: домен выражает то, что ему необходимо, на собственном языке, а слой инфраструктуры переводит эту потребность в конкретный протокол, формат и поведение поставщика. Речь не о том, чтобы скрыть API за классом с другим именем, а о том, чтобы его решения не влияли на всё приложение.
Когда интеграция уже загрязняет приложение

Связанность обычно растёт постепенно. Команда использует API из контроллера, чтобы срочно выполнить задачу; затем другой поток повторно использует того же клиента; в итоге массивы ответов и исключения SDK становятся неявными зависимостями бизнеса.
- Сценарии использования получают или возвращают массивы с именами полей поставщика.
- Бизнес-логика сравнивает внешние коды, такие как
ERR_42илиPENDING_REVIEW. - Контроллеры, команды и задачи очереди напрямую формируют HTTP-запросы.
- Исключения внешнего SDK перехватываются за пределами инфраструктуры.
- Изменение учётных данных, конечных точек или версий вынуждает редактировать несколько модулей.
- Для тестов домена нужны реальные подключения, токены или смоделированные ответы поставщика.
Эти признаки не означают, что следует начинать полную переработку. Однако они указывают, что интеграцию стоит приоритизировать по её риску: критичности для бизнеса, частоте изменений у поставщика, числу внутренних потребителей, чувствительности данных и сложности восстановления после сбоя.
Определение контрактов на языке бизнеса
Внутренний контракт, также называемый портом, должен описывать возможность, необходимую приложению, а не воспроизводить каталог операций внешнего API. Например, приложению для бронирования может требоваться «запросить бронирование», «проверить его статус» и «отменить его». Домену не нужно знать, что поставщик использует XML, OAuth, числовой идентификатор или особое соглашение о повторах.
Порт может быть выражен в виде PHP-интерфейса:
interface ReservationGateway
{
public function request(ReservationRequest $request): ReservationResult;
public function status(ReservationReference $reference): ReservationStatus;
public function cancel(ReservationReference $reference): void;
}Типы контракта должны принадлежать внутреннему языку. ReservationRequest содержит данные, необходимые для бизнес-решения; он не должен включать поля аутентификации, HTTP-заголовки или имена, унаследованные от поставщика. Аналогично внутренняя референция может инкапсулировать внешний идентификатор, не делая его доминирующим во всех сценариях использования.
Компоненты границы интеграции
Порт, адаптер и внутренний DTO
Порт — это интерфейс, который использует приложение. Адаптер — реализация, взаимодействующая с внешней системой. Между ними внутренние DTO передают данные в стабильной для приложения структуре.
Адаптер переводит в обоих направлениях: преобразует внутренний DTO в конкретный запрос и нормализует ответ в результат, который домен может интерпретировать. Если поставщик заменит guest_count на travellers, изменение должно остаться внутри этого адаптера.
Конфигурация, учётные данные и транспорт
Конечные точки, токены, тайм-ауты, сертификаты и политики повторных попыток относятся к инфраструктуре. Их следует внедрять через конфигурацию и держать вне сущностей и доменных сервисов. Также целесообразно отделить HTTP-клиент или SDK от адаптера: это упрощает замену библиотек, сбор телеметрии и тестирование маппинга без зависимости от реального транспорта.
Перевод ошибок и неопределённые состояния
Не все сбои обрабатываются одинаково. Ошибка валидации, возвращённая поставщиком, может быть исправима пользователем; сбой аутентификации требует операционного вмешательства; тайм-аут может оставить неопределённое состояние, поскольку поставщик мог обработать запрос.
Внутренний контракт должен представлять эти различия, не пропуская чужие исключения. Например, адаптер может преобразовать ответ валидации в ReservationRejected, временную проблему — в TemporaryUnavailable, а тайм-аут после отправки запроса — в UnknownSubmissionState. Последнее не следует рассматривать как простую ошибку: оно может требовать последующей проверки по ключу идемпотентности или операционной сверки.
Перевод ошибок не означает удаления деталей. Безопасно регистрируйте идентификатор корреляции, техническую причину и релевантный ответ, не раскрывая пользователю секреты или чувствительные данные.
Пример инкапсуляции сервиса бронирования
Предположим, поставщик требует JSON-запрос с датами в конкретном формате, собственным кодом отеля и заголовком авторизации. Внутренний сценарий использования не должен формировать этот запрос. Он получает запрос на бронирование, применяет свои правила и вызывает ReservationGateway.
Адаптер ExternalReservationAdapter выполняет специфические задачи:
- Преобразует внутренний идентификатор размещения в код, распознаваемый поставщиком.
- Форматирует даты, гостей и предпочтения в соответствии с внешним контрактом.
- Добавляет учётные данные и ключ идемпотентности.
- Интерпретирует HTTP-коды, тела ошибок и собственные статусы.
- Возвращает внутреннюю референцию и статус.
Приложение сохраняет правило о том, когда бронирование приемлемо; адаптер сохраняет правило о том, как запросить его у этого поставщика. Если добавляется второй поставщик, тот же порт можно реализовать при условии эквивалентности бизнес-возможности. Если это не так, принудительное применение общего интерфейса может скрыть важные различия и создать неоднозначные решения.
Как выделить уже тесно связанную интеграцию
Безопасная миграция не требует останавливать развитие продукта. Начните с инвентаризации: найдите прямые вызовы, классы SDK, внешние форматы, исключения, выходящие за пределы слоя инфраструктуры, и потребителей. Сначала определите критические пути или те, которые меняются чаще всего.
- Введите фасад: создайте порт и начальный адаптер, который сможет временно повторно использовать часть существующего клиента.
- Мигрируйте потребителей по потокам: заменяйте прямые вызовы по одному сценарию использования за раз. Не допускайте сохранения двух разных интерпретаций одной и той же ошибки.
- Централизуйте маппинг: удалите преобразования внешних полей и кодов из контроллеров, сервисов и шаблонов.
- Добавьте наблюдаемость: измеряйте латентность, результаты, нормализованные ошибки и корреляцию между внутренним запросом и внешним вызовом.
- Удалите прямой доступ: когда потребителей не останется, ограничьте или удалите предоставленный клиент, чтобы предотвратить регрессии.
Во время перехода фасад не должен превращаться в универсальный контейнер методов SDK. Его цель — определить полезную и стабильную границу, а не перенести связанность в другую папку.
Тесты и критерии проверки изоляции
Тесты домена должны использовать двойники порта. Так они проверяют бизнес-решения без сети, учётных данных и случайных особенностей поведения поставщика. Тесты адаптера, напротив, должны проверять маппинг запросов, ответов и ошибок в контролируемой среде, на имитируемом сервере или по контрактам, документированным внешней системой.
Результат можно проверить, если выполняются следующие критерии:
- Изменение формата, конечной точки или SDK сосредоточено в адаптере и его конфигурации.
- Сценарии использования зависят от внутренних контрактов, а не от HTTP-клиентов или внешних типов.
- Исключения и коды поставщика не пересекают границу.
- Неопределённые состояния имеют явную обработку, включая идемпотентность или последующую проверку, когда это необходимо.
- Бизнес-тесты выполняются с двойниками, а интеграционные тесты проверяют фактический маппинг данных.
Типичные ошибки перед подключением другого поставщика

Преждевременная абстракция — это риск: не создавайте сложную иерархию для единственной стабильной интеграции без реальной необходимости замены. Противоположная крайность также не работает: воспроизведение всего внешнего API во внутреннем интерфейсе приводит к тому, что домен наследует его сложность.
Перед интеграцией подтвердите, какая возможность нужна бизнесу, кому принадлежат данные, какие ошибки требуют действий, как предотвращается дублирование операций и что произойдёт, если ответ не придёт. Определите порт с учётом этих решений, реализуйте адаптер как переводчик и сохраняйте внешние особенности на границе. Эта дисциплина позволяет изолировать внешние интеграции в PHP, не превращая каждую смену поставщика в сквозное изменение приложения.



