...

суббота, 10 апреля 2021 г.

[Перевод] Разработка Spring Boot-приложений с применением архитектуры API First

В этом материале я приведу практический пример реализации архитектуры API First с применением спецификации OpenAPI. А именно, сначала расскажу о том, как создал определение API, а затем — о том, как, на основе этого определения, создал серверную и клиентскую части приложения. В процессе работы у меня возникли некоторые сложности, которых я тоже коснусь в этом материале.

Преимущества архитектуры API First


По мере того, как всё сильнее распространяется подход к разработке приложений, основанный на микросервисах, определённую популярность набирает и архитектура API First. У этой архитектуры имеется довольно много сильных сторон.

▍Чёткое определение контрактов


Благодаря применению архитектуры API First можно создавать точные определения контрактов, которые позволяют чётко описывать возможности, которыми должны обладать приложения. Эта архитектура, кроме того, помогает отделять интерфейс системы, предоставляемый внешним пользователям посредством API, от деталей реализации возможностей этой системы.

▍Хорошее документирование API


Применение архитектуры API First способствует тому, что разработчик, создавая API, следует правилам, изложенным в хорошо структурированной, понятной документации. Это помогает тем, кому нужно работать с API, обрести чёткое понимание особенностей интерфейса, который представлен этим API.

▍Создание благоприятных условий для распараллеливания работы над проектами


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

Теперь, когда мы обсудили сильные стороны архитектуры API First, перейдём к практике.

Практика использования архитектуры API First


Я начал работу с посещения ресурса, дающего доступ к Swagger Editor, и с создания определения моего API. Я, при описании API, пользовался спецификацией OpenAPI 3. Определения API можно создавать и с применением многих других инструментов, но я выбрал именно Swagger Editor из-за того, что у меня есть опыт создания документации для Java-проектов с использованием этого редактора. Swagger Editor мне нравится и из-за того, что он поддерживает контекстно-зависимое автодополнение ввода (Ctrl + Пробел). Эта возможность очень помогла мне при создании определения API.

Правда, я не могу сказать, что свободно владею техникой определения API, поэтому я, описывая свой API, опирался на пример petstore.

Для начала я создал весьма минималистичное определение API, описывающее создание учётной записи пользователя системы с использованием POST-запроса.


Определение API

Генерирование кода


После того, как подготовлено определение API, можно заняться созданием сервера и клиента для этого API. А именно, речь идёт о Spring Boot-приложении. Обычно я создаю такие приложения, пользуясь Spring Initializr. Единственной зависимостью, которую я добавил в проект, стал пакет Spring Web.

После этого я воспользовался Maven-плагином OpenAPI Generator, который позволяет создавать серверный и клиентский код.

▍Генерирование серверного кода


Для того, чтобы на основе определения API создать серверный код, я воспользовался соответствующим Maven-плагином, передав ему файл с описанием API. Так как мы создаём сервер, основанный на Spring, я, настраивая генератор, записал в параметр generatorName значение spring. Благодаря этому генератор будет знать о том, что он может, создавая серверный код, пользоваться классами Spring. Существует немало OpenAPI-генераторов серверного кода, их перечень можно найти здесь. Вот как выглядит конфигурационный файл плагина.

Содержимое конфигурационного файла для генерирования серверного кода

Среди настроек, которые я внёс в этот файл, хочу отметить указание имени пакета, которое будет использоваться при создании классов API и модели. Тут, в общем-то, имеется довольно много настроек, полное описание которых можно найти здесь. В частности, в подобном файле можно задать префикс имён классов модели

Коду, который генерируется плагином, нужны ещё несколько зависимостей, обеспечивающих его успешную компиляцию. Мне удалось найти минимальный набор таких зависимостей:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>${springfox-version}</version>
</dependency>
<dependency>
    <groupId>org.openapitools</groupId>
    <artifactId>jackson-databind-nullable</artifactId>
    <version>${jackson-databind-nullable}</version>
</dependency>
<dependency>
    <groupId>io.swagger.core.v3</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>${swagger-annotations-version}</version>
</dependency>

А теперь — самое интересное: генерирование кода. После выполнения команды mvn clean verify в нашем распоряжении оказываются несколько классов, сгенерированных плагином.

Результат работы генератора серверного кода

В пакете API имеются два основных интерфейса — AccountApi и AccountApiDelegate. Интерфейс AccountApi содержит текущее определение API, оформленное с использованием аннотаций @postmapping. Там же имеется и необходимая документация API, представленная в виде аннотаций Spring Swagger. Классом, реализующим этот интерфейс, является AccountApiController. Он взаимодействует со службой, которой делегирована реализация возможностей, обеспечивающих работу API.

В нашей работе весьма важен интерфейс AccountApiDelegate. Благодаря ему мы можем пользоваться паттерном проектирования «Delegate Service», который позволяет реализовывать процедуры обработки данных и механизмы выполнения неких действий, запускаемые при обращении к API. Именно в коде службы, созданной на основе этого интерфейса, и реализуется бизнес-логика приложения, ответственная за обработку запросов. После того, как будет реализована эта логика, сервер можно признать готовым к обработке запросов.

Теперь сервер готов к работе, а значит — пришло время поговорить о клиенте.

▍Генерирование клиентского кода


Для генерирования клиентского кода я воспользовался тем же плагином, но теперь в параметр generatorName я записал значение java.

Содержимое конфигурационного файла для генерирования клиентского кода

Тут же я, в разделе configOptions, сделал некоторые настройки, в частности, касающиеся библиотеки, на которой должен быть основан REST-клиент. Здесь, для реализации механизма взаимодействия с сервером, я решил использовать библиотеку resttemplate. В разделе configOptions можно использовать и многие другие настройки.

Нам, кроме того, понадобятся дополнительные зависимости:

<dependency>
   <groupId>com.google.code.gson</groupId>
   <artifactId>gson</artifactId>
   <version>${gson.version}</version>
</dependency>
<dependency>
   <groupId>io.swagger.core.v3</groupId>
   <artifactId>swagger-annotations</artifactId>
   <version>${swagger-annotations-version}</version>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>${springfox-version}</version>
</dependency>
<dependency>
   <groupId>com.squareup.okhttp3</groupId>
   <artifactId>okhttp</artifactId>
   <version>${okhttp.version}</version>
</dependency>
<dependency>
   <groupId>com.google.code.findbugs</groupId>
   <artifactId>jsr305</artifactId>
   <version>${jsr305.version}</version>
</dependency>


Результат работы генератора клиентского кода

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

Теперь надо создать экземпляр ApiClient. Он содержит параметры сервера, обмен данными с которым нужно наладить. Затем соответствующие возможности используются в классе AccountApi для выполнения запросов к серверу. Для того чтобы выполнить запрос, достаточно вызвать API-функцию из класса AccountApi.

На этом работа над клиентской частью проекта завершена. Теперь можно проверить совместную работу клиента и сервера.

Проблемы, выявленные в ходе реализации проекта


В последней версии плагина (5.0.1), которой я и пользовался, имеются некоторые проблемы.
  • При использовании генератора spring для создания контроллера в проект импортируются неиспользуемые зависимости из модуля Spring Data. В результате оказывается, что даже в том случае, если для организации работы соответствующей службы не нужна база данных, в проект, всё равно, попадают зависимости, предназначенные для работы с базами данных. Правда, если в проекте используется база данных, особых проблем вышеописанный недочёт не вызовет. Узнать о том, как продвигается работа по исправлению этой ошибки, можно здесь.
  • Обычно в разделе определения API components не описывают схемы запросов и ответов. Вместо этого в API используют ссылки на такие схемы, созданные с применением свойства $ref:. Сейчас этот механизм не работает так, как ожидается. Для того чтобы справиться с этой проблемой, можно описать встроенные (inline) схемы для каждого запроса и ответа. Это приводит к тому, что имена в модели генерируются с префиксом Inline*. Подробности об этой проблеме можно почитать здесь.

Если вы применяете более старую версию плагина, а именно — 4.3.1, то знайте о том, что в ней этих проблем нет, работает она хорошо.

В этом репозитории вы можете найти код проекта, который мы обсуждали.

Применяете ли вы в своих проектах архитектуру API First?

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий