Перейти к содержимому
DedicatedPHP Контакт

Как изолировать внешние интеграции в PHP, не загрязняя домен

Узнайте, как инкапсулировать внешние API и системы в PHP с собственными контрактами, адаптерами и поэтапным планом снижения связанности и рисков.

Редакционная схема PHP-приложения с внутренним портом и адаптерами, изолирующими несколько внешних API

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

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

Когда интеграция уже загрязняет приложение

Когда интеграция уже загрязняет приложение — guía visual de DedicatedPHP

Связанность обычно растёт постепенно. Команда использует 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, внешние форматы, исключения, выходящие за пределы слоя инфраструктуры, и потребителей. Сначала определите критические пути или те, которые меняются чаще всего.

  1. Введите фасад: создайте порт и начальный адаптер, который сможет временно повторно использовать часть существующего клиента.
  2. Мигрируйте потребителей по потокам: заменяйте прямые вызовы по одному сценарию использования за раз. Не допускайте сохранения двух разных интерпретаций одной и той же ошибки.
  3. Централизуйте маппинг: удалите преобразования внешних полей и кодов из контроллеров, сервисов и шаблонов.
  4. Добавьте наблюдаемость: измеряйте латентность, результаты, нормализованные ошибки и корреляцию между внутренним запросом и внешним вызовом.
  5. Удалите прямой доступ: когда потребителей не останется, ограничьте или удалите предоставленный клиент, чтобы предотвратить регрессии.

Во время перехода фасад не должен превращаться в универсальный контейнер методов SDK. Его цель — определить полезную и стабильную границу, а не перенести связанность в другую папку.

Тесты и критерии проверки изоляции

Тесты домена должны использовать двойники порта. Так они проверяют бизнес-решения без сети, учётных данных и случайных особенностей поведения поставщика. Тесты адаптера, напротив, должны проверять маппинг запросов, ответов и ошибок в контролируемой среде, на имитируемом сервере или по контрактам, документированным внешней системой.

Результат можно проверить, если выполняются следующие критерии:

  • Изменение формата, конечной точки или SDK сосредоточено в адаптере и его конфигурации.
  • Сценарии использования зависят от внутренних контрактов, а не от HTTP-клиентов или внешних типов.
  • Исключения и коды поставщика не пересекают границу.
  • Неопределённые состояния имеют явную обработку, включая идемпотентность или последующую проверку, когда это необходимо.
  • Бизнес-тесты выполняются с двойниками, а интеграционные тесты проверяют фактический маппинг данных.

Типичные ошибки перед подключением другого поставщика

Типичные ошибки перед подключением другого поставщика — guía visual de DedicatedPHP

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

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

Хотите применить эти идеи в своем проекте?Давайте обсудим вашу PHP-платформу.
Посмотреть связанные услуги