Интеграция с внешними сервисами

external service communication

В интернетах лежит бесконечное множество статей и мануалов на тему того, как построить красивый и годный API. Но чаще всего в работе приходится не изготавливать API, а взаимодействовать с ним.

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

 

Итак,

  1. Логгируйте входящие и исходящие запросы. Входящие пакеты должны быть залогированы до десереализации, исходящие — после сериализации, иначе детали будут искажены или утеряны. Также было бы неплохо хранить эти запросы в индексируемом хранилище (например, ELK), потому как разбирать инциденты приходится довольно часто, особенно на момент внедрения и отладки. Если вы не можете сказать что отправили партнеру или что от него приняли — будьте уверены — всех собак повесят именно на вас
  2. Не доверяйте контрактам. В нашей практике бывали случаи, когда интерфейсы изменялись на несовместимые без каких-либо предупреждений. Взгляните на пример xsd-схемы, по которой генерируются DTO ответов от сервиса.
    <xs:element name="order">
      <xs:complexType>
        <xs:sequence>
          <xs:element name="id" type="xs:string"/>
          <xs:element name="customerName" type="xs:string"/>
    ...
          <xs:element name="mcc" type="xs:string"/> 
        </xs:sequence>
      </xs:complexType>
    </xs:element>

    Эта схема не предусматривает расширения. И, конечно же, в один прекрасный момент система стала получать невалидные ответы — просто добавился новый элемент (довольно частое явление). Этого можно было бы избежать, добавив в схему <any>.

    <xs:element name="order">
      <xs:complexType>
        <xs:sequence>
          <xs:element name="id" type="xs:string"/>
          <xs:element name="customerName" type="xs:string"/>
          ...
          <xs:element name="mcc" type="xs:string"/>
          <xs:any minOccurs="0"/>
        </xs:sequence>
      </xs:complexType>
    </xs:element>

     

    Это не всегда возможно, т.к. контракты могут быть бесконечно большими, но не пренебрегайте возможностью, если она имеется. На моей памяти так же множество случаев отсутствия обязательных параметров. Здесь уже сложно что-то предпринять, остается только падать и логгировать (см п. 3). Также можно упомянуть о поддержке совместимости со стороны клиента. Убедитесь что ваш клиент/парсер/протокол поддерживают изменения схемы (например, если вы используете ObjectMapper из Jackson’а, не забудьте разрешить ему чтение неизвестных полей — @JsonIgnoreProperties(ignoreUnknown = true) или через настройки ObjectMapper’а. Более подробно о миграции и эволюции схем написано в DDIA.

  3. Валидируйте ввод. В некоторых случаях валидация по схеме невозможна (по причине её отсутствия или же если семантически различные запросы используют одни и те же структуры данных). В этом случае будет нелишним провалидировать поступившие данные. Упасть в самом начале обработки с человекопонятной ошибкой гораздо проще и дешевле, нежели искать причину NPE где-то глубоко внутри
  4. Используйте лимитеры и разрыватели цепи (circus breakers). Неоднократно приходилось наблюдать, как сервисы умирали под нагрузкой, исходящей из наших систем.
  5. Не лишним здесь будет и мониторинг. Все метрики стандартные — количество запросов в единицу времени, задержка, пропускная способность, количество запросов в очереди и т.д.
  6. Таймауты и размеры буферов должны быть выставлены явно. На подключение, на чтение и тд. Как уже было сказано выше такие сервисы не обладают высокой надежностью и сетевые проблемы для них — обычное дело. Выставляйте таймауты, если не хотите, чтобы один повисший запрос остановил всю обработку
  7. Предусмотрите возможность отключения внешнего сервиса без остановки приложения. Практика знает много случаев, когда в результате бага во внешнюю систему отправляются некорректные данные, а погасить систему полностью не представляется возможным. Сделать рычаг отключения несложно, зато это может спасти ваших коллег от сердечного приступа.external system connection switches
  8. Будет полезным использовать схемы повторения или отложенные задачи. Это может быть Spring Retry, который работает в памяти или же различные очереди и отложенные задачи, хранящиеся в базе. Здесь необходимо адекватно оценить количество повторений, частоту повторений и реакцию на невозможность выполнении задачи.
  9. Код интеграции одной системы с другой всегда выглядит ужасно — миллионы параметров с тайными знаниями и запутаными связями между собой. Спустя некоторое время даже разработчик, который является автором данного кода перестает помнить и понимать что же там происходит. Чтобы облегчить поддержку кода я придерживаюсь нескольких правил
    1. Все взаимодействие должно происходить в одной точке. Это звучит как капитанство, но иногда код доступа к внешней системе размазан по множеству компонентов. external system connection, single connectНа это есть куча отмазок — нет времени, не видел, etc, но вносить изменения становится очень затруднительно, если не сказать невозможно. Если система состоит из нескольких сервисов (н-р микросервисная архитектура), то очень часто встречается вариант когда каждый из компонентов обращается напрямую во внешнюю систему. external system connection, clientЭто также ведет к проблемам и утрате контроля над подключением. Чтобы этого избежать можно использовать прокси, который будет управлять подключениями. Туда же можно затолкать кеширование и прочие полезные вещи. Для того чтобы прокси не было единой точкой отказа можно сделать несколько реплик проксиexternal system connection proxy
    2. Протокол взаимодействия, конвертации и все трюки должны быть задокументированы до последнего байта и эта документация должна поддерживаться в актуальном состоянии
    3. Нередко доводилось писать/дорабатывать приложения — шлюзы, когда транспорт одной системы превращается в транспорт другой системы. На первый взгляд это кажется простой задачей, но со временем код становится трудноподдерживаемым за счет специфики подключаемых систем. Кроме того, количество входящих и исходящих подключений может увеличиться и вот тут наступает катастрофа. Чтобы этого не произошло, мы рекомендуем использовать промежуточный слой, который является представлением предметной области. Это трудозатратнее, но это обязательно окупается в будущем
  10. Промоделируйте негативные кейсы, такие как потеря соединения, невалидные данные и тд. Вы удивитесь сколько сюрпризов вам преподнесет система. В результате можно будет предусмотреть средства реакции на такие нестандартные ситуации — исправить это автоматически, уведомить оператора или же дать возможность исправить это вручную

Теперь поговорим о тестировании. Разберем 2 случая — юнит тестирование и end-to-end тесты.

  • С юнит-тестами всё вроде более-менее понятно, тут лучше всего использовать моки, как например MockRestServiceServer, если мы говорим о Spring. Здесь мы можем проверить как собирается и разбирается запрос. Обязательно проверьте как сериализуются/десериализуются нестандартные типы
  • С end-to-end тестированием (когда проверяется вся система целиком) дела обстоят немного сложнее. Я видел несколько основных подходов, давайте рассмотрим их здесь
    • Взаимодействие происходит с тестовой средой внешней системы.external system connection test environment Это один из самых правдоподобных сценариев, но тут есть ряд ограничений (н-р среда может быть не всегда доступна, или же по условиям контракта её нельзя использовать для автотестов). Но самый большой недостаток — это нестабильность тестов при большом количестве таких соединений. Можно сказать, что если ваше приложение обращается к десятку сервисов, то 70% билдов на билдсервере будут красные.
    • Мок внутри самого приложения (да, такое тоже бывает).external system connection test mock Стабильно в плане тестов, но не отражают реальное положение дел. Кроме того, в продакшн-коде появляется тестовый код, что не есть хорошо — это добавляет возможность некорректной работы системы в случае неправильно выставленной настройки.
    • Мок снаружи — самый оптимальный вариант. external system connection mock serverСводится он к тому, что существует некий сервер, который содержит некоторую логику и может отвечать как целевая система. Такой подход требует немного больше времени на первом этапе (нужно изготовить такой сервер), зато дает большую гибкость и стабильность тестов. При помощи этого сервера можно легко проэмулировать различные сетевые проблемы, отказы и тд.

Как видите, простая на первый взгляд задача превращается в настоящий кошмар для разработчика, с бесконечными звонками от менеджеров и бесонными ночами. Надеемся, что этот небольшой набор подсказок поможет вам сэкономить силы и время при интеграции с внешними сервисами

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *