Як ви керуєте базовою базою коду для версії API?


104

Я читав про стратегії версій API API, і те, на що жоден з них не звертається, полягає в тому, як ви керуєте базовою базою коду.

Скажімо, ми робимо купу змін, що змінюються в 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 в декількох версіях, вирішують цю проблему.


41
Дякуємо, що задали це запитання! Я не можу вірити, що більше людей не відповідають на це питання !! Мені нудно і всім, хто має думку щодо того, як версії входять до системи, але, схоже, ніхто не вирішує справжню важку проблему з відправленням версій у відповідний код. На сьогоднішній день має бути принаймні масив прийнятих "моделей" чи "рішень" цієї, здавалося б, поширеної проблеми. Існує шалена кількість запитань щодо SO щодо "версії API". Вирішити, як приймати версії - FRIKKIN SIMPLE (відносно)! ОПРАВЛІННЯ в кодовій базі, коли вона потрапляє, - ТЕРДО!
arijeet

Відповіді:


45

Я використав обидві стратегії, які ви згадуєте. З цих двох, я віддаю перевагу другому підходу, будучи простішим, у випадках використання, які його підтримують. Тобто, якщо потреби в версії є простими, тоді перейдіть з більш простим дизайном програмного забезпечення:

  • Мала кількість змін, низька складність змін або графік зміни низьких частот
  • Зміни, які значною мірою є ортогональними для решти кодової бази: публічний API може мирно існувати з рештою стека, не вимагаючи «надмірного» (для того, яке визначення цього терміна ви вирішите прийняти) розгалуження в коді

Видалити застарілі версії за допомогою цієї моделі мені не важко:

  • Хороше тестове покриття означало, що видобуток відстороненого API та пов'язаного з ним резервного коду не забезпечували (ну мінімальні) регресії
  • Хороша стратегія іменування (назви пакетів з версією API або дещо погірше, версії API у назвах методів) полегшила пошук відповідного коду
  • Наскрізні проблеми складніші; модифікації основних резервних систем для підтримки декількох API повинні бути дуже ретельно зважені. У якийсь момент вартість резервного версії версії (див. Коментар до "надмірного" вище) переважає користь однієї бази коду.

Перший підхід, безумовно, простіший з точки зору зменшення конфлікту між існуючими версіями, але накладні витрати на підтримку окремих систем, як правило, переважують користь від зменшення конфлікту між версіями. Однак це було просто просто створити новий публічний стек API і почати ітерацію в окрему гілку API. Звичайно, втрата поколінь настала майже відразу, і гілки перетворилися на безлад злиття, вирішення конфліктів та інші подібні забави.

Третій підхід - на архітектурному шарі: прийняти варіант шаблону "Фасад" і абстрагувати свої API на відкриті, відкриті шари, що спілкуються з відповідним екземпляром Facade, який, в свою чергу, розмовляє з бекендером через власний набір API. Ваш Фасад (я використовував адаптер у своєму попередньому проекті) стає його власним пакетом, автономним і перевіряється, і дозволяє вам переміщувати API інтерфейсу незалежно від бекенда та один від одного.

Це спрацює, якщо ваші версії API схильні відкривати ті самі види ресурсів, але з різними структурними уявленнями, як у вашому прикладі повного імені / імені / прізвища. Це стає дещо складніше, якщо вони починають покладатися на різні обчислення бекенду, як, наприклад, "Моя служба бекенда повернула неправильно обчислені складні відсотки, які були викриті в публічному API v1. Наші клієнти вже виправили це неправильне поведінку. Тому я не можу оновити це обчислення в бекенде і застосовувати його до v2. Тому нам тепер потрібно роздвоєти наш код для обчислення відсотків ". На щастя, це, як правило, нечасто: практично кажучи, споживачі API RESTful віддають перевагу точному представленню ресурсів над сумісністю помилок за помилкою, навіть серед неперервних змін на теоретично безсильному GETресурсі.

Мені буде цікаво почути ваше можливе рішення.


5
Цікаво, що у вихідному коді ви дублюєте моделі між v0 та v1, які не змінилися? Або у вас v1 використовується деякі моделі v0? Для мене я б розгубився, якби побачив v1, що використовує v0 моделі для деяких полів. Але з іншого боку, це зменшило б розширення коду. Для обробки декількох версій нам просто потрібно прийняти і жити з дублюючим кодом для моделей, які ніколи не змінювалися?
EdgeCaseBerg

1
Моє згадування полягає в тому, що наш вихідний код розроблений моделями незалежно від самого API, тому, наприклад, API v1 може використовувати Model V1, а API v2 також може використовувати Model V1. В основному, графік внутрішньої залежності для загальнодоступного API включав як відкритий код API, так і вихідний код "виконання", такий як код сервера та моделі. Для декількох версій єдиною стратегією, яку я коли-небудь використовував, є дублювання всього стека - гібридний підхід (модуль A дублюється, модуль B - версія) ... здається дуже заплутаним. YMMV звичайно. :)
Палпатім

2
Я не впевнений, що я дотримуюся запропонованих для третього підходу. Чи є публічні приклади коду, структурованого як він?
Ehtesh Choudhury

13

Для мене другий підхід кращий. Я використовую його для веб-сервісів SOAP і планую також використовувати його для REST.

Коли ви пишете, база коду повинна бути відома версії, але шар сумісності може використовуватися як окремий шар. У вашому прикладі база даних коду може створювати представлення ресурсів (JSON або XML) з іменем та прізвищем, але рівень сумісності змінить його, щоб мати лише ім'я.

Кодова база повинна реалізовувати лише останню версію, скажімо, v3. Рівень сумісності повинен конвертувати запити та відповіді між новітньою версією v3 та підтримуваними версіями, наприклад, v1 та v2. Рівень сумісності може мати окремі адаптери для кожної підтримуваної версії, яку можна підключити як ланцюжок.

Наприклад:

Запит клієнта v1: v1 адаптувати до v2 ---> v2 адаптувати до v3 ----> кодової бази

Запит клієнта v2: v1 адаптувати до v2 (пропустити) ---> v2 адаптувати до v3 ----> кодова база

Для відповіді адаптери функціонують просто у зворотному напрямку. Якщо ви використовуєте Java EE, ви можете, наприклад, ланцюг фільтрів сервлетів як ланцюг адаптерів.

Видалити одну версію легко, видаліть відповідний адаптер та тестовий код.


Важко гарантувати сумісність, якщо вся основна база коду змінилася. Набагато безпечніше зберегти стару базу коду для випуску помилок.
Марсело Кантос

5

Розгалуження мені здається набагато кращим, і я застосував такий підхід у своєму випадку.

Так, як ви вже згадували - зворотне виправлення помилок вимагатиме певних зусиль, але в той же час підтримка декількох версій під однією базовою базою (з маршрутизацією та всіма іншими речами) зажадає від вас не менше, але принаймні однакових зусиль, роблячи систему більше складний і жахливий з різними гілками логіки всередині (в якийсь момент версії ви визначено наштовхнетесь на величезні case()вказівки на модулі версій, що мають дублювання коду або ще гірше if(version == 2) then...). Також не забувайте, що для регресії ви все ще повинні тримати розгалужені тести.

Щодо політики версій: я б зберігав як максимум -2 версії від поточної, знецінюючи підтримку старих - це дало б певну мотивацію користувачам рухатися.


На даний момент я думаю про тестування в одній кодовій базі. Ви згадали, що тести завжди потрібно розгалужувати, але я думаю, що всі тести для v1, v2, v3 тощо можуть також жити в одному і тому ж рішенні, і всі вони повинні працювати одночасно. Я маю в виду декорування тести з атрибутами , які вказують , які версії вони підтримують: наприклад [Version(From="v1", To="v2")], [Version(From="v2", To="v3")], [Version(From="v1")] // All versions просто досліджуючи зараз, коли - небудь чув , щоб хто ж це?
Лі Ганн

1
Ну, через 3 роки я дізнався, що немає точної відповіді на оригінальне запитання: D. Це дуже залежить від проекту. Якщо ви можете дозволити заморожувати API і підтримувати його (наприклад, виправлення), я все-таки розгалужую / відключаю відповідний код (бізнес-логіка, пов'язана з API + тести + кінцева точка відпочинку) і маю всі спільні речі в окремій бібліотеці (з власними тестами) ). Якщо V1 буде співіснувати з V2 досить довгий час, а робота над функціями триває, то я б утримував їх разом і тести (охоплював V1, V2 і т.д. та назвав відповідно).
edmarisov

1
Дякую. Так, це, мабуть, досить впевнено простір. Я спершу спробую підходити до одного рішення і побачити, як це відбувається.
Лі Ганн

0

Зазвичай введення основної версії API, що призводить вас до ситуації, коли потрібно підтримувати кілька версій, - це подія, яка не (або не повинна) траплятися дуже часто. Однак цього не можна уникнути повністю. Я думаю, що в цілому є безпечним припущенням, що основна версія, щойно буде представлена, буде залишатися останньою версією протягом відносно тривалого періоду часу. Виходячи з цього, я вважаю за краще досягти простоти в коді за рахунок дублювання, оскільки це дає мені кращу впевненість у тому, щоб не порушувати попередню версію, коли я вношу зміни в останню.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.