Кое-что о версиях 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, который если не уберет так сократит количество последующих проблем с сопровождением и совместимостью.