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

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

Исходная ситуация

Давайте рассмотрим пример приложения, которое говорит «Hello» при его запуске.

> curl http://org.apisix/hello
Hello world

> curl http://org.apisix/hello/Joe
Hello Joe

Базовая технология не имеет значения  мы сосредоточимся на API части.

Использование шлюза API

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

Википедия предлагает следующее определение:

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

— API management

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

Предоставление доступа к приложению через шлюз вместо непосредственного доступа требует, чтобы вы обновили записи DNS, чтобы они указывали на шлюз, а не на приложение, и подождите, пока они не будут распространены по всему миру. Это может занять некоторое время. Чтобы следить за распространением, вы можете использовать такой сайт, как dnschecker.

Однако сначала вам нужно направить ваши HTTP-запросы от шлюза к вашему приложению. С APISIX вы можете создать маршрут, отправив HTTP-запрос на шлюз.

  1. APISIX может назначить автоматически сгенерированный идентификатор или использовать указанный вами. В нашем случае мы выбираем последнее и передаем его в URL-адресе 1 и используем глагол PUT

  2. Для обновления маршрутов нам нужно передать ключ API

  3. Именование маршрута не требуется, но позволяет нам лучше понять, что он делает

  4. Массив HTTP-методов для маршрутизации

  5. Массив URL для маршрутизации

  6. upstream — это внутреннее приложение. В нашем случае это Hello World API.

  7. Hashmap узлов с их соответствующим весом. Вес имеет смысл только при наличии нескольких узлов, что не так в нашем простом сценарии.

  8. Алгоритм балансировки для использования при настройке нескольких узлов

На этом этапе вы можете запросить шлюз и получить те же результаты, что и раньше:

> curl http://org.apisix/hello
Hello world

> curl http://org.apisix/hello/Joe
Hello Joe

Версия API

Развитие API означает, что несколько версий API должны будут сосуществовать в какой-то момент. Существует три варианта реализации версии API:

ТИП

ПРИМЕР

Параметр запроса

curl http://org.apisix/hello?version=1
curl http://org.apisix/hello?version=2

Заголовок

curl -H 'Version: 1' http://org.apisix/hello
curl -H 'Version: 2' http://org.apisix/hello

Путь

curl http://org.apisix/v1/hello
curl http://org.apisix/v2/hello

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

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

  1. Используем путь upstreams

  2. Данные для нового upstream

Нам также нужно переписать запрос, который приводит к шлюзу, который перенаправит его в upstream. Последний знает /hello, а не /v1/hello. APISIX позволяет выполнять такие преобразования, фильтры и т. д. с помощью плагинов. Давайте создадим конфигурацию плагина для преобразования пути:

  1. Используем путь plugin-configs

  2. Используем плагин proxy-rewrite

  3. Удаляем префикс версии

Теперь мы можем создать версию маршрута, который ссылается на недавно созданную конфигурацию upstream и плагина:

  1. Вот вам новый маршрут!

На этом этапе мы настроили два маршрута, один версионный, а другой неверсионный:

> curl http://org.apisix/hello
Hello world

> curl http://org.apisix/v1/hello
Hello world

Миграция пользователей с неверсионного пути на версионный

Мы версионировали наш API, но наши пользователи, вероятно, все еще используют устаревший неверсионный API. Мы хотим, чтобы они мигрировали, но мы не можем просто удалить устаревший маршрут, поскольку наши пользователи не знают о том, что он устарел. К счастью, код состояния HTTP 301 — наш друг: мы можем сообщить пользователям, что ресурс переместился с http://org.apisix/hello на http://org.apisix/v1/hello. Для этого требуется настроить плагин перенаправления на исходном маршруте:

curl http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
  "plugins": {
    "redirect": {
      "uri": "/v1$uri",
      "ret_code": 301
    }
  }
}'

Результаты интересны:

  1. Опция -L - принимать и обрабатывать перенаправления

Либо пользователи будут прозрачно использовать новую конечную точку, потому что они будут следовать по версионному пути, либо их запрос прервется, и они увидят статус 301 и описание нового местоположения API для использования.

Знайте своих пользователей

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

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

«Ядерный» вариант полностью запрещает пользователям вызывать наш API до регистрации в системе. Я предпочитаю другой вариант: ограничить количество вызовов незарегистрированных пользователей в течение фиксированного периода времени. Если они достигнут предела, мы вернем HTTP статус 429 (неизвестный) и сообщение, предлагающее им зарегистрироваться.

На момент написания этой статьи ни один готовый плагин не позволял этого добиться. Но можно написать свой. APISIX работает поверх движка Lua, и все предоставляемые плагины написаны на Lua. Кроме того, вы можете написать свои плагины на Go, Python, WebAssembly или любом другом языке на основе JVM.

Для простоты я написал Lua плагин. Поскольку целью этого поста не является понимание Lua, я не буду углубляться в него. Если вам интересен код, он доступен на GitHub. Когда вы будете готовы, вам еще предстоит выполнить пару шагов:

  1. Настройте APISIX для использования определенного каталога: config.yaml

    apisix:
      extra_lua_path: "/opt/apisix/?.lua"      

    Теперь APISIX может использовать любой скрипт Lua, расположенный в папке /opt/apisix/

  2. Загрузите плагин:

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

    curl http://apisix:9080/apisix/admin/plugins/reload -H 'X-API-KEY: xyz' 
         -X PUT 
  3. Исправьте существующую конфигурацию плагина:

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

  1. К сожалению, нам нужно повторить имеющуюся конфигурацию плагина. Команда APISIX работает над исправлением, чтобы вы могли добавить плагин в конфиг, не зная существующего.

  2. Наш плагин!

  3. Если пользователь не аутентифицирован, плагин ограничивает более одного звонка за 60 секунд. В остальном он ничем не ограничивается.

  4. Объяснение в следующем разделе

Теперь мы можем проверить, ведет ли он себя так, как ожидалось:

>curl apisix:9080/v1/hello
Hello world

>curl apisix:9080/v1/hello
{"error_msg":"Please register at https:\/\/apisix.org\/register to get your API token and enjoy unlimited calls"}

Действительно, все так как ожидалось.

Создание пользователей

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

  • Автоматизированы или требуют столько шагов ручной проверки, сколько требуется

  • Бесплатно или платно

  • Просто, как запрос адреса электронной почты без дополнительной проверки, или сложно, как запрос значительно большего количества данных

  • и т. п.

Это зависит от вашего конкретного контекста.

Что касается APISIX, в конце концов, это приводит к новому потребителю. Чтобы создать такого потребителя, нам нужно настроить плагин, который задает аутентификацию. Несколько плагинов аутентификации доступны из коробки: базовый, ключ API, JWT, OpenId, LDAP, Keycloak и т. д.

В рамках этого поста достаточно плагина аутентификации по ключу. Давайте настроим объект-потребитель, который аутентифицируется с помощью ключа API:

  1. идентификатор потребителя

  2. Используемый плагин

  3. Действительный токен mykey

Обратите внимание, что заголовок по умолчанию — apikey. Можно настроить еще один: пожалуйста, ознакомьтесь с документацией плагина key-auth.

Теперь мы можем протестировать нашу установку и убедиться, что она работает в соответствии с нашими требованиями:

>curl -H 'apikey: mykey' apisix:9080/v1/hello
Hello world

>curl -H 'apikey: mykey' apisix:9080/v1/hello
Hello world

Тестирование в производственной среде

На этом этапе мы готовы сообщить пользователям об улучшенной версии нашего API Hello world. Я предполагаю, что наша команда тщательно протестировала его, но новый код — это всегда риск. Развертывание новой версии существующего приложения с ошибками может негативно сказаться на имидже поставщика API (и на доходах!).

Чтобы свести к минимуму риски, общепринятая стратегия заключается в выпуске Canary release:

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

— CanaryRelease

Если что-то выйдет из строя, это повлияет лишь на часть пользовательской базы, и мы сможем отменить изменение без особых последствий. Однако с помощью шлюза API мы можем ввести еще онин шаг перед Canary release: мы продублируем производственный трафик на новую конечную точку API. Несмотря на то что шлюз отклонит ответ, мы сможем обнаружить дополнительные ошибки без каких-либо последствий для пользователей.

APISIX предлагает плагин прокси-зеркала для дублирования производственного трафика на другие узлы. Давайте обновим конфигурацию нашего плагина:

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix.org/register to get your API token and enjoy unlimited calls"
    },
    "proxy-mirror": {
      "host": "http://new.api:8082"             // 1
    }
  }
}'
  1. APISIX также будет отправлять трафик на этот хост

Мы можем отслеживать как новую, так и старую конечные точки, чтобы убедиться, что на новой не возникает больше ошибок, чем на старой. Если нет, мы можем исправить ошибки и снова выполнить повторное развертывание, пока не добьемся одинакового поведения. Теперь мы готовы выпустить Canary release.

Во-первых, мы создаем upstream, который указывает на новый API:

curl http://apisix:9080/apisix/admin/upstreams/2 -H 'X-API-KEY: xyz' -X PUT -d '
{
  "name": "New API",
  "type": "roundrobin",
  "nodes": {
    "newapi:8082": 1
  }
}'

Затем мы можем заменить плагин proxy-mirror на traffic-split:

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix.org/register to get your API token and enjoy unlimited calls"
    },
    "traffic-split": {
      "rules": [
        {
          "weighted_upstreams": [      // 1
            {
              "upstream_id": 2,
              "weight": 1
            },
            {
              "weight": 1
            }
          ]
        }
      ]
    }
  }
}'
  1. Отправляет 50% трафика на новый API для демонстрационных целей. В реальной жизни вы, вероятно, начнете с меньшего уровня или даже настроите только внутренних пользователей на новую конечную точку.

curl -L -H 'apikey: mykey' apisix:9080/hello
Hello world

curl -L -H 'apikey: mykey' apisix:9080/hello
Hello world (souped-up version!)

Если все работает нормально, мы можем постепенно увеличивать процент трафика, отправляемого на новый API, пока не достигнем 100%. Теперь мы можем удалить разделение трафика и перенаправить с конечной точки по умолчанию на v2 вместо v1.

Устаревание версии

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

REST не является стандартом, но у IETF есть предварительная спецификация для него. Для получения более подробной информации, пожалуйста, прочитайте Поле Deprecation HTTP-заголовка. Как следует из названия, он основан на определенном заголовке HTTP ответа.

С помощью шлюза API мы можем настроить маршрут, чтобы сообщать о его будущем устаревании и его замене. Для этого APISIX предлагает функцию response-rewrite. Хотя он может переписать любую часть ответа, мы будем использовать его для добавления дополнительных deprecation заголовков:

curl -v http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix.org/register to get your API token and enjoy unlimited calls"
    },
    "response-rewrite": {
      "headers": {
        "Deprecation": "true",
        "Link": "<$scheme://apisix:$server_port/v2/hello>; rel=\"successor-version\""
      }
    }
  }
}'
curl -v -H 'apikey: mykey' apisix:9080/v1/hello

< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 11
< Connection: keep-alive
< Date: Fri, 18 Feb 2022 16:33:30 GMT
< Server: APISIX/2.12.0
< Link: <http://apisix:9080/v2/hello>; rel="successor-version"
< Deprecation: true
<
Hello world

Заключение

В этом посте мы описали простой пошаговый процесс управления жизненным циклом ваших API:

  1. Не раскрывайте свои API напрямую; настройте API-шлюз перед ним

  2. Версионируйте существующий API, используя путь, параметр запроса или заголовок запроса.

  3. Мигрируйте пользователей с неверсионной конечной точки на версионную с помощью кода состояния 301.

  4. Мягко подталкивайте пользователей к регистрации

  5. Протестируйте API в рабочей среде, сначала продублировав трафик, а затем переведя небольшую часть пользователей на новую версию.

  6. Официальный выпуск новой версии

  7. Сообщите об устаревании старой версии через стандартные заголовки ответов.

Полный исходный код для этого поста можно найти на Github в формате maven.

Комментарии (0)