Реализация версионности API
Для публичного API, к которому обращается пользователь, принято использовать версионность, чтобы контролировать последствия. Например, когда пользователь вызывает API и ожидает определенных результатов, изменение интерфейса или поведения API может привести к непредсказуемым рискам для вызывающего пользователя. Поэтому на практике исходный API настраивается на версию 1, а модифицированный API - на версию 2.
Пользователи могут быть уверены, что обращение к оригинальному API первой версии не вызовет никаких проблем, а сервис, предоставляющий API, может продолжать итерации в области функциональности, причем обе стороны цикла разработки могут быть независимы друг от друга. Конечно, поддержка двух наборов API увеличивает трудозатраты поставщика услуг, поэтому API имеют свой жизненный цикл и не живут вечно.
Ниже приведен пример AWS Python SDK, boto3, который, согласно официальному документу, api_versions
должен быть указан в AWS configure. Изменение версии каждого сервиса доступно в этом канале.
На самом деле существует множество различных способов реализации версионности API, и одним из интересных примеров является Shopify. Из официального документа Shopify известно, что Shopify выпускает одну версию в квартал и поддерживает только четыре версии одновременно, т.е. в течение одного года.
Версии именуются по дате и встраиваются непосредственно в URL, а устаревшие версии переходят на самую старую версию из имеющихся в поддержке.
Другими словами, предположим, что четыре текущие версии выглядят следующим образом.
- 2023-07
- 2023-04
- 2023-01
- 2022-10
Тогда при обращении к 2022-07 будет использоваться версия 2022-10.
Это интересный способ реализации, так как у клиента есть год на внесение изменений, и если он этого не сделает, то система все равно будет работать, но с неожиданными результатами, а не просто рухнет.
Как реализовать такой механизм версионирования? В данной статье представлен возможный подход.
Подход к реализации
Полный исходный текст находится в следующем репозитории.
https://github.com/wirelessr/versioning
Общая архитектура реализации выглядит следующим образом.
Все три службы реализуют два URI: /hello
и /hi
, которые просто выводят URI с номером версии.
Для пользователей, чтобы вызвать соответствующий сервис, достаточно поставить префикс перед URI, например, curl http://localhost/v2/hello
выведет "hello v2".
Кроме того, при вызове версии, не входящей в белый список (v1, v2 и v3), произойдет возврат к версии v1, например, curl http://localhost/v4/hello
выведет "hello v1".
Ядром этого эксперимента является nginx на шлюзе.
http {
server {
listen 80;
location ^~ /v1/ {
rewrite /v1/(.*) /$1 break;
proxy_pass http://web_v1;
}
location ^~ /v2/ {
rewrite /v2/(.*) /$1 break;
proxy_pass http://web_v2;
}
location ^~ /v3/ {
rewrite /v3/(.*) /$1 break;
proxy_pass http://web_v3;
}
location ~ /v(\d+)/ {
rewrite /v(\d+)/(.*) /$2 break;
proxy_pass http://web_v1;
}
}
}
Если местоположение соответствует предыдущим трем правилам, то переписываем исходный URI, удаляем префикс и перенаправляем на соответствующий сервис, если не соответствует предыдущим правилам, но соответствует спецификации версии (v плюс integer), то все равно перенаправляем на сервис v1.
Используя nginx regex, мы можем сделать так, чтобы версия соответствовала соответствующему сервису, и реализовать дополнительный механизм fallback.
Заключение
На самом деле существует и другой подход к предоставлению различных версий API в оригинальном сервисе, например, открытие нескольких конечных точек непосредственно в API-сервисе следующим образом.
- /v1/hello
- /v2/hello
- /v3/hello
Создавать три версии API напрямую вместо добавления префикса к API через шлюз не рекомендуется. Если не изолировать их физически, то это приведет к большим накладным расходам на разработку, как в следующем реальном примере.
Когда нам нужно изменить поведение общей версии библиотеки, это неизбежно затронет v1
, что затрудняет итерации по функциональности, а также вносит риск для пользователя.
Поэтому изоляция на физическом уровне более управляема, чем изоляция на логическом уровне. В данной статье приведен один из возможных подходов, но существует множество других реализаций, позволяющих достичь того же результата, поэтому не стесняйтесь поделиться ими со мной.