Админка как юнит тест для HTTP API
Приветствую, коллеги. Множество сервисов в интернет предоставляют HTTP API для разработчиков. Есть много статей как это сделать правильно, не меньшее количество рассказов как получилось неправильно, и могучая кучка критики что получилось у других. Хорошее API сделать трудно — оно постоянно пытается выпасть из кошелька миллера, обзавестись циклическими зависимостями среди сущностей и засунуть бедного разработчика в прокрустово ложе сценариев использования “как их видят разработчики”. Свои пять копеек добавлю и я — под катом забавный, но рабочий способ, который мы используем для укрощения нашего немаленького HTTP API
На просторах интернета я часто встречаю утверждение, что удачные фреймворки и API были извлечены авторами из работающих, коммерческих проектов. Как возник фреймворк Django? Авторы делали множество новостных сайтов. В какой-то момент они поняли, что часть кода переползает из проект в проект. Вынесли этот код в отдельную библиотеку — получился веб фреймворк. Как возникло Amazon API? Делали интерфейсы взаимодействия между внутренними сервисами. Через некоторое время API стабилизировался, стал меняться все меньше и меньше и, наконец, его сделали доступным для внешних клиентов.
Извлекая что-то из живого проекта нам нет необходимости пытаться “на бумаге” расписать варианты использования, что обычно получается не очень хорошо. Несмотря на костыли “мозговых штурмов”, диаграмм и wiki, люди не очень хорошо приспособлены к удерживанию в голове новых сложных систем, и множество попыток придумать сложный API “на бумаге” оканчивается полной переделкой через несколько месяцев после релиза.
Но что делать, если проект изначально является платформой, и никаких “коммерческих проектов”, из которых можно извлечь его части, в природе пока не существует? Многие менеджеры в таких случаях предлагают командам разработчиков делать “макеты” потенциальных решений и разрабатывать API для таких макетов. Увы, придуманный “из головы” макет чаще всего сильно отличается от того, что будут создавать реальные пользователи. Не потому, что плохо придумывали, а из-за ограничений нашего мозга на удерживаемый в фокусе внимания объем информации.
Много лет назад, когда мы только начинали создавать voximplant, у нас была большая стопка хотелок от клиентов и понимание того, что с первого раза хорошее API сделать не получится. Но очень хотелось :). И во время очередного мозгового штурма родилась странная идея: сделать админку для разработчиков не на backend, как тогда делали для большинства проектов, а на frontend. И чтобы админка для всех своих функций использовала то же HTTP API, что мы даем нашим клиентам.
Огромный плюс такого подхода заключается в том, что дизайн API эволюционирует вместе с админкой. В начале это чистый лист с кнопкой “добавить пользователя”. Чтобы кнопка работала, мы делаем AJAX вызов и метод /AddUser со стороны сервера. Добавляем поле ввода идентификатора пользователя — добавляем парный аргумент user_name для метода API — и так далее, пока не будет создана основная функциональность.
Безусловно, такой подход подойдет не для всех. Разработка “софта” — очень большая область. Есть разработка по спецификации заказчика, когда все API целиком дизайнится специально обученной командой архитекторов. Есть ситуации, когда API отражает лишь небольшую часть функциональности платформы, доступную для внешних разработчиков “песочницу”. Есть разработка по контракту, когда все нужно продумать и рассчитать заранее, чтобы спрогнозировать сроки и бюджет.
Тем не менее, нам этот способ очень хорошо помог. И, насколько я вижу в интернете, все больше и больше сервисов с развитым API придерживаются принципа “eat your own dog food”, используя эти API и для разработки собственных админок и сервисов. Пока у стартапа нет исчерпывающей документации и обучающих материалов на все случаи жизни, на вопрос клиента “а как сделать так-то” всегда можно ответить — “сделайте действия в админке и посмотрите в chrome dev tools какие запросы и с какими аргументами она выполняет”. Такой подход работает — и работает хорошо.
Админка — штука хорошая, но для многих сервисов она позволяет делать только наиболее популярные операции, тогда как “сложные” кейсы остаются под капотом или доступны по API. В качестве примера я покажу нашу API функцию /AttachPhoneNumber которая позволяет арендовать номер телефона, чтобы в дальнейшем с помощью javascript сценариев управлять приходящими на этот номер звонками. В админке при покупке номера можно отфильтровать доступные номера по странам/типам/городам, выбрать понравившийся номер или указать количество номеров для аренды, а затем кликнуть на кнопку “купить”:
Если посмотреть на нашу документацию для соответствующего метода API, то можно легко найти соответствие между тем, что мы видим в интерфейсе и тем, что можно передать в API функцию:
- Разные варианты авторизации. Они одинаковы для большинства методов API и описаны в начале документации.
- В phone_count передается количество номеров, которые мы хотим арендовать за один запрос: от одного и до… сколько есть.
- В country_code при передается код страны, для России это будет “RU”, полный список можно посмотреть… да, в той же админке:
- В phone_category_name передается категория номера: прямой городской, мобильный, 8-800- и так далее. Список категорий также можно подсмотреть в админке или получить для указанной страны с помощью функции API GetPhoneNumberCategories — например, для городских номеров России это будет “GEOGRAPHIC”.
- В phone_region_id передается “идентификатор региона”. Это некий внутренний идентификатор, который используют телекомы разных стран, предоставляющие номера. Список традиционно можно подсмотреть, но лучше все-таки получить с помощью специального API GetPhoneNumberRegions, которое возвращает список доступных регионов для указанной страны и категории номера.
- В телефонной адресации некоторых стран региона недостаточно, и для таких стран при покупке нескольких номеров в параметре country_state передается идентификатор штата. Список штатов можно получить с помощью GetPhoneNumberCountryStates — для тех стран, где они есть.
Как можно видеть, все эти значения являются отражениями соответствующих полей формы и добавлялись по мере добавления функциональности: сначала была возможность покупать по номеру, затем мы добавили выбор количества номеров, который потянул за собой необходимость указывать какие номера интересуют клиента, затем появились партнеры и возможность покупки по номеру устарела — и так далее.
Практический пример, как создатели приложений на нашей платформе могут использовать API, руководствуясь админкой как эталонной реализацией. Предположим, мы хотим арендовать себе номер в Токио и выполнить javascript при звонке на этот номер. Исключительно в демонстрационных целях, конечно же.
Для начала нам понадобится выяснить количество доступных номеров — вдруг Токио закончился? Для авторизации используем идентификатор аккаунта и ключ из настроек админки voximplant, а для запроса количества номеров — специально обученный метод GetPhoneNumbers:
curl "https://api.voximplant.com/platform_api/GetNewPhoneNumbers/?account_id=1&api_key=2&country_code=JP&phone_category_name=GEOGRAPHIC&phone_region_id=8384"
В ответ получает ошибку авторизации json, в котором поле total_count содержит количество доступных номеров, на момент написания статьи — 224 штуки. Номера в Токио есть в количестве. Для аренды номера используем уже описанный мной метод AttachPhoneNumber точно так же, как это делает админка. От предыдущего запроса этот будет отличаться только названием api endpoint и параметром phone_count, указывающим что мы хотим арендовать ровно один номер:
curl "https://api.voximplant.com/platform_api/AttachPhoneNumber/?account_id=1&api_key=2&country_code=JP&phone_category_name=GEOGRAPHIC&phone_region_id=8384&phone_count=1"
После пары секунд раздумий мы получим JSON с параметрами арендованного номера, выглядит он вот так:
{
"result":1,
"phone_numbers":[
{
"phone_number":"81345793488",
"verification_status":"VERIFIED",
"phone_id":9033
}
]
}
В ответе мы можем видеть сам номер (мне достался 81345793488). В этом номере “+81” — код Японии, “3” — код Токио (обратите внимание — он не совпадает с идентификатором регионом для Токио, который мы задавали как “8384”, потому что идентификатор региона — это внутренний идентификатор телекома) и “45793488” — собственно сам номер. Чтобы принять на него звонок и сделать что-нибудь полезное нужно связать его с одним из приложений voximplant — которые тоже можно создавать через API. Пример такой команды, которая свяжет арендованный номер с приложением “foo” (да, у меня есть такое приложение. Оно говорит “привет” и вешает трубку):
curl "https://api.voximplant.com/platform_api/BindPhoneNumberToApplication/?account_id=1api_key=2&phone_number=81345793488&application_name=foo"
Надеемся, что этот небольшой кейс добавит в вашу копилку приемов веб разработки небольшой трюк — возможно, когда-нибудь в будущем такой подход спасет вас от множества бессонных ночей, проведенных за дизайном “идеального api”. В комментариях я традиционно готов ответить на неконструктивную критику, ответить на каверзные вопросы и просто пообщаться на тему дизайна и разработки API.