software-architecture
June 23, 2023

Кое-что о версиях API

Наверное это опять будет выглядеть как «срыв покровов», но описываемая проблема — важная, особенно если вас угораздило стать руководителем проекта, связанного с разработкой ПО, а собственного опыта программирования нет совсем.

Капитан Очевидность.

Срыв покровов

Зайду с главного:

никаких «версий API» на самом деле не существует.

Вот так сурово да.

Нет я не #бнулся, к доктору мне не надо и я очень хорошо представляю о чем пишу. Прекрасно понимаю что такое заявление без примеров не оценят, так что читайте и смотрите.

Допустим, есть некий HTTP-сервер, поддерживающий как устаревший HTTP/1.0 так и новые HTTP/1.1 и даже модные HTTP/2 и HTTP/3.

Как это технически работает с точки зрения реализации?

Через ветвления логики в коде:

поддержку разных версий протокола обеспечивают условия if-else в самом коде сервера.

Запомните этот момент, это важно.

Теперь идем на уровень выше по дереву абстракций — допустим есть некий вебсервис, отдающий два разных набора методов по REST, с разными префиксами.

Что-то вроде классического:

/api/v2/listCustomers

и

/api/v3/listCustomersExtended

Разумеется все это было реализовано лишь ради того чтобы устаревшие клиенты продолжали использовать старые мерзкие методы, а новые — «светлый и блестящий» v3.

Кстати если не замечали:

почему-то никогда не бывает v4 и v5 версий, ни разу не видел чтобы версионирование через префикс переживало 3ю версию. Ни разу.

Наверное есть какая-то важная причина?

И такая причина действительно существует — сама реализация:

все версии API так или иначе обслуживает одно и тоже приложение.

Даже если у вас «микросервисы» и вы сделали два автономных бекэнда — два разных приложения, каждое из которых отвечает за свою версию — они все равно пересекутся в общих данных.

Надо нереально заморочиться, чтобы создать действительно автономные версии  — от API до данных.

Это дорого, долго и практически всегда бесмыссленно (помните про общие данные?), поэтому на практике все что дальше входного API с версией представляет собой одно и то же приложение.

Теперь примеры.

Вот как выглядит типичное решение с поддержкой «нескольких версий» API на самом банальном Java + Spring Boot:

/**
   REST Контроллер для старой версии API
*/
@RestController
@RequestMapping("/api/v1") 
public class OldApiController {
/**
 Отдаем список работников с использованием устаревших DTO
*/
@GetMapping("/employees") 
public List<Employee> getAllEmployees() { 
    // при этом используем общую логику выборки из базы данных
   return sharedEmployeeService.getAllEmployees(); 
   }
}
/**
 REST-контроллер для новой версии API, с новыми и блестящими функциями
*/
@RestController
@RequestMapping("/api/v2") 
public class NewApiController {
/**
Отдаем список работников по-новому (!) 
*/
@GetMapping("/employeesExtended") 
public List<ExtendedEmployee> getAllEmployeesExtended() { 
    // сначала получаем список в старом формате (тк рефакторить долго и сложно)
   final List<Employee> emps= sharedEmployeeService.getAllEmployees(); 
     // затем преобразуем в новый светлый формат (!) и отдаем наружу
     return emps.isEmpty()? Collections.emptyList() : extendedEmployeeService.extendList(emps); 
   }
}

Поэтому даже если снаружи торчит v1, v2 и v3 — обслуживает весь этот зоопарк один и тот же код.

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

Почему?

Потому что новая версия API не означает новую версию приложения — изоляции нет

Как появлются такие версионные префиксы?

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

Создавая тем самым для вас иллюзию изоляции и ощущение что ничего не сломается.

Теперь переходим к самому важному — к тестированию API с версиями.

Тестирование

Вы не одиноки в своей вере в версии API — существует целый раздел в тестировании ПО (QA), под названием «интеграционное тестирование», адепты которого выстраивают всю свою работу вокруг версий API.

То есть на свете есть люди, которые тратят 8 оплаченных часов рабочего времени каждый день на выяснение того что методы API версии 2.25 соотвествуют спецификации версии 2.25.

И затем еще пишут красивые и длинные отчеты.

Вообщем, моя практика показывает что любое интеграционное тестирование можно спокойно выкинуть из проекта, а всех кто таким занят либо заставить заниматься use-case тестами (симуляция действий пользователя) либо выгнать на мороз.

Потому что это не работает и работать не может по определению.

Возвращаясь к примеру с версиями:

   // сначала получаем список в старом формате (тк рефакторить долго и сложно)
   final List<Employee> emps= sharedEmployeeService.getAllEmployees(); 
     // затем преобразуем в новый светлый формат (!) и отдаем наружу
     return emps.isEmpty()? Collections.emptyList() : extendedEmployeeService.extendList(emps); 

Если у вас что-то сломается внутри общего метода getAllEmployees() — как версии API вам помогут?

Никак.

Спросите ваших программистов на досуге: собираются ли они при создании новой версии API использовать существующие методы логики и как это повлияет на стабильность — отличный способ выбесить ваших разработчиков)

Выводы

Вообщем самое главное что вы как руководитель ИТ-проекта вынести из этой статьи:

даже если ваши разработчики и аналитики используют слово «версия» предлагая какое-то решение — речь всегда идет про единое цельное приложение, даже разделенное на микросервисы.

И тестировать придется все приложение целиком, поскольку версии API это лишь ветвления его логики.

Если вы начинаете играть с версиями API — умножайте затраты на тестирование на количество версий.

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