Рубрика развивается при поддержке
Разработка
Media Aviata
296

Рекомендации при подключении API

Как мы интегрировали в наш проект много разношерстных API, с какими проблемами столкнулись и какие решения нашли.

В закладки

Об авторе:

Владислав Балаклейский, Технический директор Aviata.kz

Сегодня расскажу о том, как мы интегрировали в наш проект много разношерстных API, какие проблемы мы выловили и какие выводы из этого сделали.

Aviata.kz - это сервис по покупке авиабилетов. Как проходит продажа билета:

  • для начала мы обращаемся к API GDS (поставщика билетов), ищем, бронируем;
  • затем проводим оплату клиента через API платежной системы;
  • готовый билет отправляем по API email сервиса;
  • отправляем нотификацию о покупке на телефон через API SMS сервиса и Push через API Firebase.

Как вы понимаете, весь наш сервис построен на работе с API.

В рамках этой презентации я условно разбиваю API на 2 типа: доступные и закрытые.

Под доступными подразумеваются в первую очередь открытые, широко распространенные, хорошо документированные API, с большим комьюнити. За примерами далеко ходить не придется и вы о них наверняка слышали. Мы используем AWS SES, Google Drive, Firebase. К тому же очень часто для их использования уже есть набор готовых клиентов под разные языки программирования. Также есть комьюнити и возможность вести обсуждение или искать решение багов в stack overflow.

Закрытые API - это противоположность доступным. Amadeus, Radixx, Travelfusion, Gallileo, Instant Payment — это названия API GDS (Global Distribution System), поставщиков авиа и жд билетов. Вот именно на подключении этих ребят мы и набили шишки. Я буду говорить о разных особенностях этих API, все они не обладают полным списком недостатков, это не кошмарные системы. Но в каждом из них есть изъяны, поэтому будем говорить в совокупности об их проблемах.

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

Зачастую доступ к таким API и к документации открывается только после подписания договора. Получается что у нас нет открытого комьюнити, где мы можем обсудить проблемы или коллективно решить баги.

Когда я составил для себя этот список проблем, я понял что такие же симптомы есть и в наших собственных API. Давайте пройдемся по деталям и найдем к ним верное решение.

Подготовка к работе

При работе с интеграцией у вас может не быть на руках доступа к документации до момента подписания договора с поставщиком API. А как только договор подписан, то время будет идти против вас. Но что у вас точно должно быть - это ваш пошаговый процесс как вы будете интегрировать API в соответствии с MVP.

Что же нужно запросить от поставщика, чтобы как можно скорее начать работу.

  • Документация.
  • Примеры запросов на каждый пункт вашего процесса в соответствии MVP — от Авторизации до успешной выписки билета
  • Доступы в тестовую среду. Получая доступы в тестовую среду надо сразу же их проверить.
  • Доступы в production среду. Далеко не всегда вам их дадут, но если дали, то надо их проверить.
  • Договориться о кол-ве часов для воркшопа, даже если поставщик их не предоставляет. Воркшоп очень поможет вам, особенно в моментах описанных в проблеме с параметрами API. В режиме онлайн вам будет проще объяснить и показать ваши вопросы. Чтобы ваш воркшоп прошел продуктивно подготовьте повестку с детализированными вопросам, которые будете обсуждать и отправьте поставщику заранее.
  • Узнать о процессе сертификации в деталях. Процесс сертификации у каждого поставщика идет по разному. Кому-то достаточно будет только примеров запросов к API. А кто-то может потребовать полноценную тестовую среду с интерфейсом, чтобы они могли самостоятельно проверить работу API. Вы должны быть к этому готовы.
  • Договориться, кто и как будет вести вашу техническую поддержку со стороны поставщика и обменяться контактами. Если для этого выделят отдельно 2 специалистов, которые будут в курсе ваших процессов интеграции - это ускорит процесс. Не придется каждый раз объяснять ваши проблемы новому сотруднику

Ранее мы с вами говорили о том, чтобы писать клиент на основе самого сложного и разнообразного кейса в вашем процессе. Но когда вы получили доступы в тестовую среду, получили документацию и примеры запросов я советую провести тест всего процесса с минимальным количеством данных. Взять Postman или аналогичный инструмент для отправки запросов и отправить сырые запросы например (XML) от авторизации до выписки билетов. Если MVP требует реализации возвратов билетов, проверьте и их тоже. Почему это важно? На стороне поставщика не всегда все сразу настроено как положено. Для нас с вами это черный ящик, мы даже не подразумеваем какие у них есть настройки, локали, и т.д. Лучше сразу понять что не работает из всего процесса, чтобы поставщик это исправил, настроил, пока вы только начинаете интеграцию.

Технические проблемы с реализацией

Неописанные параметры

Когда приходит задача интегрироваться с сложным API, очень часто хочется как можно скорее просто начать работать. Это может быть ошибкой.Например, делая одну из интеграций мы по готовой инструкции сделали запрос на поиск из Алматы в Нур-Султан на 1 пассажира. Получили результаты с перелетами, и даже забронировали билет. Все это сделали сразу в клиенте.

Но для перелета туда - обратно все стало сложнее.

Работая над одной из интеграций, мы использовали инструкцию-пример для интеграции. В запросе на поиск перелета из Алматы в Нур-Султан помимо стандартных данных — город вылета, прилета, дата, и кол-во пассажиров , у нас был параметр FareFilterMethod, который никак не влияет на выдачу, если вы делаете запрос в одну сторону.

У этого параметра такое количество значений:

CombinabilityNestedRoundtripAllFares

CombinabilityNestedRoundtripLowestFarePerDay

CombinabilityNestedRoundtripLowestFarePerFareType

CombinabilityNestedRoundtripLowestFarePerFlight

CombinabilityNestedRoundtripOnlyAllFares

CombinabilityNestedRoundtripOnlyLowestFarePerDay

CombinabilityNestedRoundtripOnlyLowestFarePerFareType

CombinabilityNestedRoundtripOnlyLowestFarePerFlight

NoCombinabilityAllFares

NoCombinabilityRoundtripLowestFarePerDay

NoCombinabilityRoundtripLowestFarePerFareType

NoCombinabilityRoundtripLowestFarePerFlight

NoCombinabilityRoundtripOnlyAllFares

NoCombinabilityRoundtripOnlyLowestFarePerDay

NoCombinabilityRoundtripOnlyLowestFarePerFareType

NoCombinabilityRoundtripOnlyLowestFarePerFlight

Стоит ли говорить о том, что описания к этим значениям в API нет. Не зная, на что он в принципе влияет, мы использовали то, что было дано в примере и вроде бы все пошло хорошо. Нам вернулись результаты поиска:

ALA -> TSE 27000KZT 9:53

ALA -> TSE 29000KZT 9:53

ALA -> TSE 24300KZT 15:00

TSE -> ALA 29000KZT 16:00

TSE -> ALA 23300KZT 20:20

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

Оказывается все это уже готово на стороне API и мы можем для этого использовать параметр CombinabilityNestedRoundtripOnlyLowestFarePerFareType сразу. Вот только этот параметр меняет структуру выдачи и теперь она выглядит так:

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

ALA -> TSE 27000KZT 9:53

TSE -> ALA 29000KZT 16:00

TSE -> ALA 23300KZT 20:20

ALA -> TSE 29000KZT 9:53

TSE -> ALA 29000KZT 16:00

TSE -> ALA 23300KZT 20:20

ALA -> TSE 24300KZT 15:00

TSE -> ALA 23300KZT 20:20

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

Не используются полные реальные данные

Похожая ситуация произошла и для бронирования пассажиров. С бронированием взрослых и детей не было проблем. Но для бронирования младенца в дополнительных параметрах надо проставлять своего взрослого. Это привело к переписыванию клиента в части бронирования.

Например, для интеграции сервиса ЖД мы использовали свои данные для бронирования, однако уже в релизе столкнулись с тем, что Казахские символы не отображались в PDF билете, который генерировался из HTML. Пришлось экстренно костылить библиотеку, которая сначала генерировала docx файл с казахским шрифтом, а уже оттуда делали PDF.

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

Размер данных

Старые API очень часто используют XML\WSDL форматы. Как по мне это устаревшие и сложные для чтения\восприятия форматы. И к тому же, они многословные, что по итогу приводит к большому объему данных. Например, некоторые GDS на результаты поиска выдают 1.6 мб данных, а если запросить результаты на соседние даты, то получится 20 мб. Это накладывает свой отпечаток на скорость обработки. Поэтому когда мы использовали библиотеку suds для обработки SOAP\WSDL результатов, у нас все тормозило. Пришлось переключиться на стандартную низкоуровневую библиотеку xml. Работать с ней не так приятно, но скорость важнее. Поэтому проверьте какой объем данных вы получаете от API и сравните производительность разных библиотек.

Тоже самое касается и хранения данных. В результате бронирования нам приходит огромный пласт данных в XML. Не все эти данные нам нужны для использования в проекте, поэтому мы выделяем нужные и сохраняем в удобном нам формате. Раньше мы распределяли их по сущностям в классы с большой вложенностью, практически как в исходном XML ответе. А затем сохраняли сериализованный класс в базу. Это отстой, не делайте так. Потому что с годами ваши классы обрастают новыми методами и атрибутами, и когда спустя время вы десериализуете старую бронь у нее не будет этих методов. Лучше переводите все в JSON структуру. Весит меньше, легче читается.

Авторизация

В 2 из 5 случаях подключения к API у нас возникали трудности с авторизацией. Обычно до запуска в продакшн на моменте тестирования вы отправляете запросы и API сервис реагирует вполне нормально. Отдает ответ вовремя, без задержек и ошибок. Что происходит, когда вы выкатываете ваш сервис в продакшн? Параллельно пойдут сотни и тысячи запросов в API.

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

Во втором случае авторизационная сессия становилась невалидной после процесса бронирования. Поэтому для каждого нового процесса (от поиска до бронирования) надо было запрашивать новую сессию.

Так что внимательно и детально уточняйте процесс работы с авторизацией.

Заключение

В заключении у нас получается что-то вроде чек-листа, который поможет избежать множества сложностей в подключении API. Это также сэкономит время и нервы разработчиков.

Итоговый чек-лист

  • Подготовьте ТЗ с описанными шагами для интеграции;
  • Составьте наиболее полный список запросов для интеграции;
  • Работайте над интеграцией с максимально возможным кол-вом данных;
  • Проверяйте скорость работы клиента;
  • Проработайте авторизацию как можно надежнее;
  • Получите и проверьте доступы в тестовую и production среду как можно раньше;
  • Готовьтесь к сертификации заранее;
  • Заложите больше времени на отладку клиента в production;
  • Запустите нагрузочное тестирование вместе с поставщиком API

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

Материал опубликован пользователем.
Нажмите кнопку «Написать», чтобы поделиться мнением или рассказать о своём проекте.

Написать
{ "author_name": "Media Aviata", "author_type": "self", "tags": [], "comments": 0, "likes": 1, "favorites": 9, "is_advertisement": false, "subsite_label": "dev", "id": 94407, "is_wide": true, "is_ugc": true, "date": "Tue, 26 Nov 2019 08:48:57 +0300", "is_special": false }
Облачная платформа
Основа для цифровизации бизнеса
0
{ "id": 94407, "author_id": 402266, "diff_limit": 1000, "urls": {"diff":"\/comments\/94407\/get","add":"\/comments\/94407\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/94407"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 235819, "last_count_and_date": null }
Комментариев нет
Популярные
По порядку
{ "page_type": "article" }

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fizc" } } }, { "id": 4, "label": "Article Branding", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "cfovx", "p2": "glug" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjog" } } }, { "id": 10, "disable": true, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "disable": true, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "bscsh", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-223676-0", "render_to": "inpage_VI-223676-0-1104503429", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=bugf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Баннер в ленте на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudx", "p2": "ftjf" } } }, { "id": 16, "label": "Кнопка в шапке мобайл", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byzqf", "p2": "ftwx" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvc" } } }, { "id": 19, "disable": true, "label": "Тизер на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "p1": "cbltd", "p2": "gazs" } } }, { "id": 20, "label": "Кнопка в сайдбаре", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "cgxmr", "p2": "gnwc" } } } ] { "page_type": "default" }