Я читал о стратегиях управления версиями для API-интерфейсов ReST, и кое-что из них, похоже, не решает, как управлять базовой кодовой базой.
Скажем , мы делаем кучу ломки изменений в API - например, изменяя наш ресурс клиента так , чтобы он возвращал отдельным forename
и surname
поле вместо одного name
поля. (В этом примере я буду использовать решение для управления версиями URL, поскольку его легко понять, но вопрос в равной степени применим к согласованию содержимого или настраиваемым заголовкам HTTP)
Теперь у нас есть конечная точка http://api.mycompany.com/v1/customers/{id}
и другая несовместимая конечная точка http://api.mycompany.com/v2/customers/{id}
. Мы по-прежнему выпускаем исправления и обновления безопасности для API v1, но разработка новых функций теперь сосредоточена на v2. Как мы пишем, тестируем и внедряем изменения на нашем сервере API? Я вижу как минимум два решения:
Используйте ветку / тег системы управления версиями для кодовой базы v1. v1 и v2 разрабатываются и развертываются независимо, слияния системы контроля версий используются по мере необходимости для применения одного и того же исправления ошибок к обеим версиям - аналогично тому, как вы управляете кодовыми базами для собственных приложений при разработке новой основной версии, при этом по-прежнему поддерживая предыдущую версию.
Сделайте так, чтобы сама база кода знала о версиях API, чтобы в итоге вы получили единую базу кода, которая включает в себя как представление клиента v1, так и представление клиента v2. Рассматривайте управление версиями как часть архитектуры вашего решения, а не проблему развертывания - возможно, используя некоторую комбинацию пространств имен и маршрутизации, чтобы гарантировать, что запросы обрабатываются правильной версией.
Очевидным преимуществом модели ветвления является то, что старые версии API тривиально удалять - просто прекратите развертывание соответствующей ветки / тега - но если вы используете несколько версий, вы можете получить действительно запутанную структуру ветвей и конвейер развертывания. Модель «унифицированной кодовой базы» позволяет избежать этой проблемы, но (я думаю?) Значительно усложнит удаление устаревших ресурсов и конечных точек из кодовой базы, когда они больше не нужны. Я знаю, что это, вероятно, субъективно, поскольку вряд ли будет простой правильный ответ, но мне любопытно понять, как организации, поддерживающие сложные API-интерфейсы в нескольких версиях, решают эту проблему.
источник
Ответы:
Я использовал обе упомянутые вами стратегии. Из этих двух я предпочитаю второй подход, более простой в случаях использования, которые его поддерживают. То есть, если потребности в управлении версиями просты, выберите более простой дизайн программного обеспечения:
Я не нашел слишком сложным удалить устаревшие версии с помощью этой модели:
Первый подход, безусловно, проще с точки зрения уменьшения конфликта между сосуществующими версиями, но накладные расходы на поддержку отдельных систем, как правило, перевешивают преимущество уменьшения конфликта версий. Тем не менее, было очень просто создать новый общедоступный стек API и начать работу над отдельной веткой API. Конечно, потеря поколений наступила почти сразу, и ветви превратились в беспорядок слияний, разрешения конфликтов слияния и других подобных забав.
Третий подход - на архитектурном уровне: принять вариант паттерна фасада и абстрагировать свои API-интерфейсы в общедоступные, версионные уровни, которые взаимодействуют с соответствующим экземпляром фасада, который, в свою очередь, общается с серверной частью через собственный набор API. Ваш фасад (я использовал адаптер в своем предыдущем проекте) становится отдельным пакетом, самодостаточным и тестируемым, и позволяет вам переносить интерфейсные API независимо от серверной части и друг от друга.
Это будет работать, если ваши версии API имеют тенденцию предоставлять одни и те же виды ресурсов, но с разными структурными представлениями, как в примере с вашим полным именем / именем / фамилией. Становится немного сложнее, если они начнут полагаться на разные внутренние вычисления, например: «Моя внутренняя служба вернула неправильно рассчитанные сложные проценты, которые были представлены в общедоступном API v1. Наши клиенты уже исправили это неправильное поведение. Поэтому я не могу обновить это вычисление в серверной части и применить его до версии 2. Поэтому теперь нам нужно форкнуть наш код расчета процентов ". К счастью, это, как правило, нечасто: практически говоря, потребители RESTful API предпочитают точное представление ресурсов, а не обратную совместимость с ошибками, даже среди неразрывных изменений теоретически идемпотентного
GET
ресурса.Мне будет интересно услышать ваше окончательное решение.
источник
Для меня второй подход лучше. Я использую его для веб-служб SOAP и планирую использовать его также для REST.
Когда вы пишете, кодовая база должна учитывать версию, но уровень совместимости может использоваться как отдельный уровень. В вашем примере кодовая база может создавать представление ресурса (JSON или XML) с именем и фамилией, но уровень совместимости изменит его, чтобы вместо этого было только имя.
Кодовая база должна реализовывать только последнюю версию, скажем, v3. Уровень совместимости должен преобразовывать запросы и ответы между последней версией v3 и поддерживаемыми версиями, например v1 и v2. Уровень совместимости может иметь отдельные адаптеры для каждой поддерживаемой версии, которые могут быть подключены цепочкой.
Например:
Запрос клиента v1: v1 адаптироваться к v2 ---> v2 адаптироваться к v3 ----> codebase
Запрос клиента v2: v1 адаптироваться к v2 (пропустить) ---> v2 адаптироваться к v3 ----> кодовая база
Для ответа адаптеры работают просто в обратном направлении. Если вы используете Java EE, вы можете, например, использовать цепочку фильтров сервлетов как цепочку адаптеров.
Удалить одну версию легко, удалите соответствующий адаптер и тестовый код.
источник
Мне кажется, что ветвление намного лучше, и я использовал этот подход в своем случае.
Да, как вы уже упоминали, исправление ошибок обратного переноса потребует некоторых усилий, но в то же время поддержка нескольких версий в одной исходной базе (с маршрутизацией и всем остальным) потребует от вас если не меньше, но, по крайней мере, таких же усилий, что сделает систему больше сложный и чудовищный с различными ветвями логики внутри (в какой-то момент управления версиями вы определенно придете к огромным
case()
указателям на модули версий, у которых есть дублированный код или даже хужеif(version == 2) then...
). Также не забывайте, что для целей регрессии вам все равно нужно держать тесты разветвленными.Что касается политики управления версиями: я бы сохранил максимум -2 версии от текущей, устаревшую поддержку старых - это дало бы некоторую мотивацию для перехода пользователей.
источник
[Version(From="v1", To="v2")]
,[Version(From="v2", To="v3")]
,[Version(From="v1")] // All versions
просто исследуя сейчас, когда - либо слышал , чтобы кто же это?Обычно введение основной версии API, приводящее вас к ситуации, когда необходимо поддерживать несколько версий, является событием, которое не происходит (или не должно) происходить очень часто. Однако полностью избежать этого нельзя. Я думаю, что в целом можно с уверенностью предположить, что после появления основной версии она будет оставаться последней в течение относительно длительного периода времени. Исходя из этого, я бы предпочел добиться простоты кода за счет дублирования, поскольку это дает мне больше уверенности в том, что я не нарушу предыдущую версию, когда я внесу изменения в последнюю.
источник