Вашему вниманию предлагается перевод поста о том, как разрабатывался Windows Azure API: о трудностях, удачных и неудачных решениях, сделанных выводах. Далее — текст автора.
После разговора с моим коллегой по работе о REST API мне пришла в голову идея рассказать о своем опыте организации и работы команды, которая создала Windows Azure Service Management API. На написание этого поста меня вдохновили такие великолепные статьи в жанре “чему я научился” как эта за авторством Foursquare и эта от Даниэля Джакобса из Netflix.
Предупреждение: Все рассказанное под катом — мое личное мнение. Я даже не уверен, что остальные участники команды со мной согласны. И я точно знаю, что некоторые из высказанных мыслей довольно противоречивы.
Предисловие
Windows Azure Service Management API (SMAPI) было создано в 2008 году и позволяло разработчика контролировать свой аккаунт Windows Azure — деплой нового кода, смена настроек, масштабирование сервисов, управление хранилищами ит.д. Кстати, вот документация. Я не могу называть конкретные цифры, но могу сказать что API ежедневно обрабатывало огромное количество запросов от разных клиентов — сред разработки, утилит, систем автоматизации, ботов злоумышленников и еще от множества других источников. Мне повезло работать над этим API вместе с потрясающей командой (пользуясь случаем, передаю привет Zhe, Dipa, Frank!) и я горд тем, что нам удалось создать.
Чему я научился за это время?
Именование REST URI не играет роли… почти
Говоря это, я рискую навлечь на себя гнев многих друзей и коллег. В самом начале работы над API мы потратили недели, чтобы создать максимально “чистый” дизайн URL. Все мы прочли работу Fielding’а и для нас было нормой размахивать этой книгой во время особо жарких обсуждений. Но в конце концов я понял — мы просто зря теряли время.
Формат URI+HTTP verb+wire не имеет значения
И вот почему. Разработчики, которые будут пользоваться вашим API, не будут видеть HTTP запросы и ответы. Если ваше API будет хоть сколько нибудь успешно, то для него будут сделаны биндинги к языкам программирования — вами или же сообществом. Конечно, возможность проснуться в три часа ночи и по памяти соорудить curl запрос сделает жизнь разработчиков биндингов чуть проще. Так же как и игнорирование базовых идиом REST (к примеру, изменяющие состояние GET запросы) заставит разработчиков страдать. Но если каждое второе совещание скатывается на обсуждения использовать ли PUT или POST, и это длится неделями — то вы теряете свое время на академические дискуссии.
Доказательства? Посмотрите на API Амазона (не S3, а все остальное что там есть). Все эти API — самые уродливые XML чудовища, сделанные в RPC-стиле. Но вы же не будете отрицать насколько это API успешно?
Начинайте с того, как ваше API будет использоваться
Одной из лучших вещей, которые мы сделали во время работы над API было то, что мы начали с кода, использующего этот API, и, посмотрев на него, сделали REST интерфейс. Так как самого API еще не было, то мы просто писали псевдокод на разных языках программирования для важных сценариев. Это позволило нам избежать большого количества недостатков, которые были неочевидны во время простого чтения спецификации API. К примеру, мы обнаружили, что пытаемся заставить разработчиков создавать слишком много промежуточных объектов между осмысленными вызовами API. Мы также обнаружили, что у пользователей нет возможности отослать обратно полученные через API структуры без необходимости их модифицировать. Без предварительной проверки псевдокодом у нас не было бы возможности обнаружить все эти недостатки, которые бы всплыли гораздо позже и их исправление было бы намного дороже. Или еще хуже — мы могли сделать релиз такого API и быть долгое время использующего к неудачному дизайну.
Мы ввели правило, что разработчик не предлагает API функций, в комментариях к которым нет хотя бы нескольких строчек псевдокода чтобы понять как они работают. Это правило также улучшило совещания о дизайне API, когда разработчики могли видеть примеры использования вместо обычных списков параметров и возвращаемых значений.
Плохой способ создать API фокусируется на REST интерфейсе ко внутренностям сервиса. Получившееся в результате “API” обычно бросают благодарным пользователям, через достаточно высокую стену чтобы не слышать что они об этом думают (примечание переводчика — идеоматическое выражение, «to throw over the wall»). Мы видим вокруг множество таких API, созданных Microsoft и другими компаниями, и все эти API причиняют разработчикам боль.
Не заставляйте разработчика много думать
Старайтесь свести к минимуму количество “сущностей”, которые ваше API отдает наружу. Каждая концепция, которой оперирует ваше API, увеличивает сложность понимания для разработчиков и крайне негативно влияет на кривую обучения. Мы придерживались агрессивной политики по урезанию торчащих наружу “сущностей”, даже если приходилось объединять немного отличающиеся “сущности” в одну. Так же агрессивно мы урезали количество операций, доступных для каждой “сущности”. Все это привело к значительному упрощению API по сравнению с самым первым дизайном.
Сделайте ключевые действия быстрыми и простыми
Одна из проблем, с которой мы столкнулись на поздних стадиях дизайна API известна под названием “проблема N+1”. Эта проблема возникает, когда для доступа к дочерним объектам нужно сперва получить их список у родительского объекта, после чего сделать отдельный HTTP запрос к каждому полученному. В нашем случае самой популярной операцией был запрос сервисов Windows Azure с последующим получением их статуса. Так как мы уже готовились к релизу, то у нас не оставалось времени на изменение дизайна API, так что мы сделали то, что я назвал “огромным костылем” — параметр “recursive” для родительского объекта. Удивительно, но этот костыль оказался очень эффективным и удобным решением — как для пользователей нашего API, так и для наших серверов. Еще одна подобная штука — “частичные ответы”, то, что сейчас поддерживается в рамках GData. В каждом таком случае реализация была не самой удачной, но сама идея затачивания API под ключевые сценарии использования очень нам помогла. Не удивлюсь, если эти “костыли” в нашем дизайне экономят миллионы API вызовов каждый день. И позволяют быстрее создавать удобные клиенты к нашей платформе.
Одним из хороших способов достигнуть таких результатов является разработки прототипов клиентов параллельно разработке самого API. К примеру, мы поддерживали версии клиентов для публикации сборок, клиентов для мониторинга и клиентов для других популярных сценариев. Это удивительно, насколько плох становится ваш свежеприготовленный API как только вы начинаете писать код, его использующий. Как дизайнер API, лучше чтобы вы увидели это до того, как до вашего API доберутся благодарные пользователи.
Измеряйте и логируйте все
Что у нас действительно хорошо получалось — это проводить измерения. Мы инструментировали наше API для снятия метрик и очень внимательно следили за получающимися в результате цифрами. Каждый день мы знали что делают пользователи, какие самые популярные вызовы API, самые частые ошибки и так далее. Это дало нам понимание как на самом деле используется наше API (к примеру, это позволило сказать сколько вызовов сэкономил хак N+1). При релизе новой функциональности мы отслеживали как она используется и быстро вносили коррективы, если суровая правда жизни отличалась от того использования, которое мы предполагали. И, возможно, самое важное — у нас была возможность отслеживать с какими ошибками сталкиваются пользователи и внимательно изучать их. Мы часто добавляли или изменяли сообщения об ошибках, чтобы пользователям было понятно что происходит при работе с системой. В течении долго времени все это помогало нам улучшать юзабилити API.
Версионирование и расширяемость
API меняются, и трудно предсказать какие это будут изменения. Иногда изменения небольшие — вы хотите добавить поле в объект или еще один элемент к списку элементов. Другие изменения сложнее — вы хотите изменить механизм аутентификации или прекратить поддерживать старую версию API. В любом случае вы будете благодарны себе если с самого начала позаботитесь о версионировании. Есть много способов защитить свое API от проблем, связанных с изменениями. Можно пойти нашим путем: клиент отсылает в заголовке поддерживаемую им версию API и сервер демонстрирует ожидаемое для этой версии поведение. Другой способ — добавить номер версии в URL REST методов. В любом случае, добавление любого способа защиты в самую первую версию API избавит вас от большой головной боли впоследствии.
Отдельно стоит рассмотреть “небольшие изменения” в API. Думаю, что мы могли бы с самого начала сделать Azure SMAPI лучше. К примеру, API Foursquare использует интересный способ косвенного расширения структур:
{ type: "special", item: { special: { ...specialdata... } } }
Это позволяет им добавлять новые “типы” не теряя совместимости с существующими клиентами, которые такие типы не ожидают. Жалко, что мы не сделали что-то похожее для Azure API. Это бы серьезно упростило нам жизнь в случае мелких изменений и избавило бы от необходимости постоянно увеличивать номер версии и ломать совместимость со старыми клиентами.
Sriram Krishnan
me@sriramk.com
Автор: Voximplant