Коммуникация правит миром. Взаимодействие необходимо и между людьми, и между программным обеспечением. Хотите адекватного ответа на ваш запрос к приложению? API вам в помощь! Необходимость в реализации API возникает практически во всех проектах, и со временем мы задумываемся, можно ли улучшить текущий API? Последовательность конкретных шагов и реальные примеры наш рецепт создания рабочего API-проекта.
Первый вопрос, который нужно задать: А точно ли стоит реализовать новую версию API? Возможно, уже работающая версия отвечает всем необходимым вам критериям. Но если вы уже для себя ответили да на поставленный вопрос, то читайте дальше и найдете наш ответ. Если вы ответили нет, то тоже читайте дальше и применяйте наш опыт проектирования и реализации в следующих ваших проектах.
Итак, какие же задачи могут стать поводом к разработке новой реализации API. В рамках развития программных продуктов, например, в сфере комплексной безопасности, как у нас, периодически появляется необходимость ускорить процесс клиентской и серверной разработки, или снизить временные затраты на поддержку и развитие API, или упростить поддержку обратной совместимости с помощью версионности API, а также добавить автогенерацию документации API. Или все эти задачи одновременно.
Какие шаги можно предпринять для решения этих задач, и как выйти на новый уровень зрелости API можно узнать в этой статье. Реальный алгоритм действий при разработке новой реализации API, описанный здесь, поможет в создании собственного проекта, а возможно станет предметом дискуссии. Комментарии профессионалов приветствуются!
Рассмотрим на примере API для работы с котиками то, как мы совершенствовали один из наших проектов.
Как понять, что стоит реализовать новую версию API
Для того чтобы понять, что API пора спасать, необходимо пройтись по следующим пунктам:
-
определить уровень зрелости API;
-
проверить, есть ли у API версионность;
-
проверить наличие документации API.
Разберем каждый пункт по отдельности, чтобы точно определиться, стоит ли игра свеч или и так сойдет.
Определим уровень зрелости API
Для этого идеально подходит модель Леонарда Ричардсона, в которой он выделяет четыре уровня зрелости API:
-
Уровень 0: Один URI и один HTTP метод (в основном метод POST);
-
Уровень 1: Несколько URI и один HTTP метод;
-
Уровень 2: Несколько URI, каждыи из которых поддерживает разные HTTP методы;
-
Уровень 3: HATEOAS. Ресурсы сами описывают свои возможности и взаимосвязи.
Если API соответствует 0 или 1 уровню зрелости, то определенно есть куда расти, потому что:
-
Если используется один URI, то не понятно с каким ресурсом работаем;
-
Не понятно, что делаем с ресурсом, так как используется один HTTP метод;
-
Использование одного URI создает трудности с документацией API;
-
Использование одного URI создает трудности с логированием входящих запросов;
-
Из-за использования одного URI, информация о типе ресурса передается в теле запроса.
Посчитаем, сколько если у нас получилось, и определим направление разработки. Это поможет сконцентрироваться на задачах и четко сформулировать дальнейшие шаги.
Проверим, есть ли у API версионность
Затем необходимо проверить версионность. Это позволит реализовывать изменения в текущем API, а при необходимости даст возможность включать расширения в новую версию API. Кроме того, она позволит серверу облегчить обеспечение обратной совместимости.
Благодаря версионности можно также ранжировать документацию по версиям, чтобы разработчики клиента всегда могли определить, насколько у них актуальная документация. Согласитесь, это удобно разработчикам, тестировщикам и сотрудникам технической поддержки.
Рассмотрим 3 основных способа версионирования API и разберем подробнее каждый из них. Современные разработчики выделяют следующие способы:
- Использование разных URI (Uniform Resource Identifier);
- Использование параметра запроса;
- Использование заголовка, Accept Header/Media Type.
Каждый из приведенных способов версионирования имеет свои плюсы и минусы. И для каждого конкретного случая реализации API необходимо оценить и выбрать оптимальный способ или их комбинацию.
-
Использование разных URI простой в проектировании, реализации и документировании способ версионирования. Однако он имеет целый ряд недостатков:
-
приводит к загрязнению URI, так как префиксы и суффиксы добавляются к основным строкам URI;
-
разбивает существующие URI, то есть все клиенты должны обновиться до нового;
-
приводит к увеличению размер HTTP кэша для хранения нескольких версии;
-
создает большое количество дубликатов URI, может снизить производительность приложения из-за увеличения количества обращении к кэшу;
-
является крайне негибким и не позволяет просто изменить ресурс или небольшой их набор.
Пример:
GET v1/cats/{name}
2.Использование параметра запроса позволяет легко документировать версионность и рекомендуется к использованию в случае, если важно HTTP кэширование. Однако и этот способ приводит к загрязнению пространства URI.
Пример:
GET cats/{name}?version=v1
3. Использование заголовка и Accept Header/Media Type также легко документировать и, в отличие от предыдущих способов не приводит к загрязнению пространства URI. Но и у этого способа выделяют несколько минусов:
-приводит к неправильному использованию заголовков, так как они изначально предусматривались не для версионирования;
-требует для управления версиями на основе заголовка и типа медиа использования таких инструментов, как Postman, или создания автоматизированных средств, добавляющих необходимый заголовок в HTTP запрос.
Пример:
GET cats/{name}
Headers: version=v1
Выбор конкретного способа целиком лежит на плечах разработчика, исходя из поставленной задачи и навыков. Каждый вариант вполне рабочий, но имеет свою специфику и особенности реализации.
Проверим наличие документации API
Даже к фену удобно иметь описание, не говоря уже о серьезной проекте разработки ПО. Поэтому наглядное описание API всегда удобно для использования как backend, так и frontend разработчиками. Документация может быть реализована, например, с помощью Swagger (фреймворк для спецификации RESTful API), он дает возможность не только интерактивно просматривать спецификацию, но и отправлять запросы с помощью Swagger UI:
Немного окунувшись в теорию и предварительный анализ, переходим непосредственно к проектированию и реализации API. Наше API отвечал следующим критериям:
-
соответствует 0 уровню зрелости (Один URI и один HTTP метод);
-
невозможно достоверно установить, с каким ресурсом API работает и какие функции выполняет;
-
отсутствуют автоматизированные средства документации API, что приводит к неполной документации API или ее полному отсутствию для некоторых запросов;
-
появляются сложности с поддержкой обратной совместимости, так как нет версионности API.
Для большего понимания того, что не нравилось, приведем пример того, как выглядел наш API.
Пример:
POST /cats - должен вернуть котика по имени Пушок (гарантируется, что у
requestBody: { котиков уникальные имена);
"name": "Pushok"
}
POST /cats - должен вернуть вернуть список белых котиков;
requestBody: {
"color": "white"
}
Цели реализации нового API
Если в предыдущем пункте мы уверенно выявили хотя бы один пункт в существующем API, то самое время переходить к реализации нового. Основными целями, которые преследует разработчики при создании API являются:
-
Ускорение процесса клиентской и серверной разработки;
-
Снижение временных затрат на поддержку и развитие API;
-
Добавление автогенерации документации API;
-
Поддержка версионности API для упрощения поддержки обратной совместимости с помощью версионности API.
Таким образом, проект получает автономность от его создателя, гибкость в масштабировании и апгрейде и, зачастую, серьезно выигрывает по производительности в целом.
Повышаем уровень зрелости API
Для решения вышеперечисленных проблем и достижения целей было принято решение о переходе на 2 уровень зрелости API. Это позволило понять, с каким ресурсом идет текущая работа и что с ним необходимо сделать, плюс появилась возможность документировать API.
Для того, чтобы повысить API с 0 на 2 уровень зрелости были проанализированы все текущие проблемы и выделены следующие пункты:
1.Разделение текущего API на смысловые части для выделения соответствующего ресурса для каждой части;
2.Использование методов, соответствующих действиям над ресурсами: GET, POST, PUT, DELETE;
3.Обозначение ресурса во множественном числе;
Пример:
GET /cats - должен вернуть список котиков
GET /cats/Pushok - должен вернуть котика по имени Пушок
(гарантируется, что у котиков уникальные имена)
4. Указание фильтрации в параметрах.
Пример:
GET /cats?color=white - должен вернуть список белых котиков
Добавляем версионность
После повышения зрелости API выходим на новый этап и выбираем способ версионирования. Для текущего проекта был выбран способ версионирования с использованием собственного заголовка. Это решение не попадает в пункт неправильное использование заголовков, так как будет использоваться собственный заголовок. Для удобства было решено указывать версии вида 2.n.
Для начала реализуем контроллер:
После этого для реализации версионности, создадим enum:
Далее создадим конвертер, используя внутренний Spring Framework интерфейс Converter<S,T>. Он преобразует исходный объект типа S в целевой типа T, в нашем случае преобразует текстовое значение версии в тип Enum ApiVersion до попадания в контроллер:
Если в заголовке было отправлено значение 2.0, до контроллера оно дойдет уже в виде v2. При такой реализации вместо того, чтобы перечислять все доступные версии, можно будет указать в заголовке, что ожидается enum. А контроллер после добавления версионирования будет выглядеть так:
При дальнейшем развитии API и реализации новых версии, список всех существующих версии будет храниться в enum. Когда для конкретной новой версии потребуется уникальная реализация, то в контроллере можно будет указать версию явно.
Указание версии было решено сделать обязательным.
Документируем
И наконец переходим к документированию. Критерии выбора конкретного инструмента были основаны на собственном опыте и дискуссии с коллегами-разработчиками. В ходе обсуждения был выбран популярный и широко используемый инструмент автогенерации API Swagger. Он не является единственным для решения задач документирования, но в нашем случае был признан оптимальным, так как бесплатен, прост для освоения и обладает необходимым набором функций для грамотного документирования API.
Рассмотрим пример реализации документации для созданного выше контроллера. Для этого реализуем интерфейс:
С его помощью можно добавить название и описание API текущего контроллера, описание каждого запроса, плюс есть возможность указать схему тела запроса. Остальную информацию Swagger получает из аннотаций контроллера.
Перейдя в Swagger UI увидим задокументированное API:
Получим более подробную информацию:
Преимущества новой версии API
На примерах выше был продемонстрирован переход с 0 уровня зрелости API на 2 уровень зрелости согласно модели Ричардсона, благодаря этому мы получили:
-
повышение уровня зрелости API, что дало понимание, с каким ресурсом мы работаем и что с ним делаем;
-
версионирование, которое позволит вносить изменения в текущий API;
-
удобное документирование и появление внятной документации, да и документации вообще.
В итоге и на стороне сервера, и на стороне клиента теперь можно увидеть актуальный API с полным описанием методов, моделей, кодов ответов, параметров запроса и версий. Это значительно упростит разработку новых версий в будущим и сэкономит время на разбор чужого кода.
Скорость разработки при переходе на новую версию API значительно возросла, ведь разработчикам стало значительно удобнее и приятнее работать. Наш путь разработки оказался вполне рабочим и его смело можно рекомендовать для применения в реальных проектах. А может в вашем арсенале есть другие варианты? Готовы обсудить!