Нельзя просто так взять и сделать версионирование API

50
НЕЛЬЗЯ ПРОСТО ТАК ВЗЯТЬ И СДЕЛАТЬ ВЕРСИОНИРОВАНИЕ API github.com/ikalnytskyi

Transcript of Нельзя просто так взять и сделать версионирование API

Page 1: Нельзя просто так взять и сделать версионирование API

НЕЛЬЗЯ ПРОСТО ТАК ВЗЯТЬ И СДЕЛАТЬВЕРСИОНИРОВАНИЕ API

github.com/ikalnytskyi

Page 2: Нельзя просто так взять и сделать версионирование API

МОТИВАЦИЯЗабота о пользователях

Page 3: Нельзя просто так взять и сделать версионирование API

Tweetbot

Gwibber

TweetDeck

Twitter API

IFTTT.com

RememberTheMilk.com

Page 4: Нельзя просто так взять и сделать версионирование API

МОТИВАЦИЯЗабота о пользователяхОбнаружение поддерживаемого функционала

Page 5: Нельзя просто так взять и сделать версионирование API

{ "owner": "John Doe", "product": { ... } }

{ "owner": { "firstName": "John", "lastName": "Doe" }, "product": { ... } }

{ "owner": { "firstName": "John", "lastName": "Doe", "phone": "+1 234 567 89" }, "product": { ... } }

Page 6: Нельзя просто так взять и сделать версионирование API

import requests

response = requests.get('https://api.not-ebay.com/sales') entity = response.json()

if isinstance(entity['owner'], dict): if 'phone' in entity['owner']: # Последняя версия API, предоставляем весь # полный набор функционала. else: # Предпоследняя версия API, предоставляем # ограниченный набор функционала. else: # Боль. :'( Самая старая версия API, предоставляем # только базовый набор функционала.

Page 7: Нельзя просто так взять и сделать версионирование API

МОТИВАЦИЯЗабота о пользователяхОбнаружение поддерживаемого функционалаОбновление с минимальным временем простоя

Page 8: Нельзя просто так взять и сделать версионирование API

Сервер #1

nova-api

nova-conductor

nova-scheduler

python-novaclient

nova-compute

Сервер #2

nova-api

nova-conductor

nova-scheduler

python-novaclient

nova-compute

Сервер #3

nova-api

nova-conductor

nova-scheduler

python-novaclient

nova-compute

Коммуникатор(Load Balancer, AMQP, etc)

Page 9: Нельзя просто так взять и сделать версионирование API
Page 10: Нельзя просто так взять и сделать версионирование API

СПОСОБЫ ВЕРСИОНИРОВАНИЯ HTTP APIВерсионирование при помощи URI

Page 11: Нельзя просто так взять и сделать версионирование API

GET /v1.42/resource HTTP/1.1 Host: api.example.com

Page 12: Нельзя просто так взять и сделать версионирование API

СПОСОБЫ ВЕРСИОНИРОВАНИЯ HTTP APIВерсионирование при помощи URIВерсионирование при помощи параметра запроса

Page 13: Нельзя просто так взять и сделать версионирование API

GET /resource?version=1.42 HTTP/1.1 Host: api.example.com

Page 14: Нельзя просто так взять и сделать версионирование API

СПОСОБЫ ВЕРСИОНИРОВАНИЯ HTTP APIВерсионирование при помощи URIВерсионирование при помощи параметра запросаВерсионирование при помощи HTTP заголовка

Page 15: Нельзя просто так взять и сделать версионирование API

GET /resource HTTP/1.1 Host: api.example.com Api-Version: 1.42 Vary: Api-Version

Page 16: Нельзя просто так взять и сделать версионирование API

СПОСОБЫ ВЕРСИОНИРОВАНИЯ HTTP APIВерсионирование при помощи URIВерсионирование при помощи параметра запросаВерсионирование при помощи HTTP заголовкаВерсионирование при помощи Content Negotiation

Page 17: Нельзя просто так взять и сделать версионирование API

GET /resource HTTP/1.1 Host: api.example.com Accept: application/vnd.project+json; version=1.42

Page 18: Нельзя просто так взять и сделать версионирование API
Page 19: Нельзя просто так взять и сделать версионирование API

ВЕРСИОНИРОВАНИЕ ПРИ ПОМОЩИ CONTENTNEGOTIATION

Заголовок Accept и RFC 7231

Page 20: Нельзя просто так взять и сделать версионирование API

GET /resource HTTP/1.1 Host: api.example.com Accept: application/xml; q=0.2, application/json, application/* Accept: application/vnd.project+json; version=1; q=0.7 Accept: application/vnd.project+json; q=0.9; version=2

Page 21: Нельзя просто так взять и сделать версионирование API

ВЕРСИОНИРОВАНИЕ ПРИ ПОМОЩИ CONTENT NEGOTIATION

Content-Type дилемма

Page 22: Нельзя просто так взять и сделать версионирование API

POST /resource HTTP/1.1 Host: api.example.com Accept: application/vnd.project+json; version=1 Content-Type: ???

{ "owner": "John Doe", "product": { ... } }

Page 23: Нельзя просто так взять и сделать версионирование API

POST /resource HTTP/1.1 Host: api.example.com Accept: application/vnd.project+json; version=1 Content-Type: application/vnd.project+json; version=1

{ "owner": "John Doe", "product": { ... } }

Page 24: Нельзя просто так взять и сделать версионирование API

POST /resource HTTP/1.1 Host: api.example.com Accept: application/vnd.project+json; version=1 Content-Type: application/vnd.project+json; version=2

{ "owner": { "firstName": "John", "lastName": "Doe" }, "product": { ... } }

Page 25: Нельзя просто так взять и сделать версионирование API

POST /resource HTTP/1.1 Host: api.example.com Accept: application/vnd.project+json; version=1 Content-Type: application/json

{ "owner": "John Doe", "product": { ... } }

Page 26: Нельзя просто так взять и сделать версионирование API

Согласно :RFC 7231

Заголовок Accept определяет предпочтения по типупредставления ответа от сервера, включаязапрашиваемый ресурс и возможные ошибки.

Заголовок Content-Type определяет типпредставления передаваемого в теле сообщенияданных.

Page 27: Нельзя просто так взять и сделать версионирование API

ОХ.. МОЖЕТ ПОПРОБОВАТЬ HTTP ЗАГОЛОВОК?

Page 28: Нельзя просто так взять и сделать версионирование API

ВЕРСИОНИРОВАНИЕ ПРИ ПОМОЩИ HTTPЗАГОЛОВКА

Какой выбрать код ответа если запрашиваемаяверсия API не найдена?

400 Bad Request404 Not Found406 Not Acceptable412 Precondition Failed

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

Page 29: Нельзя просто так взять и сделать версионирование API

ХМ.. А ЧТО С ПАРАМЕТРОМ ЗАПРОСА?

Page 30: Нельзя просто так взять и сделать версионирование API

ВЕРСИОНИРОВАНИЕ ПРИ ПОМОЩИПАРАМЕТРА ЗАПРОСА

Традиционно используются совместно с методомGET.

Некоторые веб-фреймворки могут быть не готовы ктакому порядку вещей.

Page 31: Нельзя просто так взять и сделать версионирование API

django 1.10

@require_http_methods(['POST']) def create_user(request): version = request.GET.get('version', LATEST_VERSION)

Page 32: Нельзя просто так взять и сделать версионирование API

ОК, ПОПРОБУЕМ URI

Page 33: Нельзя просто так взять и сделать версионирование API

ВЕРСИОНИРОВАНИЕ ПРИ ПОМОЩИ URI

Не требует никаких специальных возможностейфреймворка. В простом случае, каждая версия -отдельный обработчик.

Самый популярный способ версионирования HTTPAPI.

Page 34: Нельзя просто так взять и сделать версионирование API

ВЕРСИОНИРОВАНИЕ И ВЕБ-ФРЕЙМВОРКИ

Фреймворки не решают проблему версионированияAPI.

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

Page 35: Нельзя просто так взять и сделать версионирование API

djangorestframework 3.5.3

from rest_framework import views, versioning

class CreateUser(views.APIView):

versioning_class = versioning.QueryParameterVersioning

def post(self, request, format=None): if request.version == '42': pass

Page 36: Нельзя просто так взять и сделать версионирование API

DIY: ВЕРСИОНИРОВАНИЕ И ВЕБ-ФРЕЙМВОРКИ

Подход к версионированию тесно связан с подходомк организации кода.

Один обработчик принимающий версию API?Много обработчиков вызываемых в зависимости отверсии?

В сочетании со способами передачи версииклиентом, имеем немалое количество вариантов.

Page 37: Нельзя просто так взять и сделать версионирование API

Решение задачи обычно сводится к написаниюсобственного middleware или роутера.

Page 38: Нельзя просто так взять и сделать версионирование API
Page 39: Нельзя просто так взять и сделать версионирование API

ЭВОЛЮЦИЯ ДАННЫХ

Эволюция API - это следствие эволюции данных.

Расширение функциональности зачастую ведет кэволюции данных.

Page 40: Нельзя просто так взять и сделать версионирование API

database = [ {'owner': 'John Doe', 'product': some_product_data}, ]

@app.route('/v1/sales') def get_sales_v1(): return jsonify(database)

Page 41: Нельзя просто так взять и сделать версионирование API

database = [ {'owner': {'firstName': 'John', 'lastName': 'Doe'}, 'product': some_product_data}, ]

@app.route('/v1/sales') def get_sales_v1(): conv_owner = lambda o: '{firstName} {lastName}'.format(**o) return jsonify([ {'owner': conv_owner(record['owner']), 'product': record['product']} for record in database ])

@app.route('/v2/sales') def get_sales_v2(): return jsonify(database)

Page 42: Нельзя просто так взять и сделать версионирование API

ЭВОЛЮЦИЯ ТЕСТОВВместе с эволюцией данных, следует эволюция тестов.

Page 43: Нельзя просто так взять и сделать версионирование API

Каждая версия API должна быть покрыта тестамидабы гарантировать что все работает так как иработало, а формат запроса/выдачи не поменялся.

Изменение схемы данных требует адаптациифейковых данных в тестах.

По возможности не использовать mock притестировании версий API.

Page 44: Нельзя просто так взять и сделать версионирование API

# Актуальный формат: # # {'owner': # {'firstName': 'John', # 'lastName': 'Doe'}, # 'product': some_product_data}, # _database = [ {'owner': 'John Doe', 'product': some_product_data}, ]

@mock.patch('project.database', _database) def test_get_sales_v1(app): with app.test_client() as c: assert c.get('/v1/sales').json() == { 'owner': 'John Doe', 'product': some_product_data, }

Page 45: Нельзя просто так взять и сделать версионирование API

ЭВОЛЮЦИЯ БИЗНЕС ЛОГИКИ

Разные версии API могут требовать разной версиибизнес логики.

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

Следуя принципу DRY, очень легко оказаться в адунаследований, стратегий и сложных интерфейсов.Альтернатива – copy-paste подход.

Page 46: Нельзя просто так взять и сделать версионирование API

def assign_ips_lt_5_0(...): pass

def assign_ips_eq_5_0(...): pass

def assign_ips_gt_6_1(...): pass

Page 47: Нельзя просто так взять и сделать версионирование API

ВЫВОДЫИх нет. Каждый их должен сделать сам. :)

Page 48: Нельзя просто так взять и сделать версионирование API

МОИ ВЫВОДЫ

Наличие нескольких версий API существенноувеличивает цену поддержки.

Не поддерживать все множество существующихверсий. Выбрать стратегию. Например, поддерживатьпоследние N версий.

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

Page 49: Нельзя просто так взять и сделать версионирование API

Использовать HTTP заголовок для версионированиякак самый простой и гибкий способ.

Хранить тесты на каждую поддерживаемую версиюAPI. Не использовать mock.

Page 50: Нельзя просто так взять и сделать версионирование API

СПАСИБО ЗА ВНИМАНИЕ!ВОПРОСЫ?