Статьи

Двигаясь в ногу со временем: на пути к внедрению OpenAPI v3.0.0 в API JAX-RS

Страшно видеть, как быстро проходит время! Спецификация OpenAPI 3.0.0 , основная модернизация спецификации Swagger , так что привыкать к ней, была выпущена в основном год назад, но потребовалось некоторое время, чтобы инструменты наверстали упущенное. Тем не менее, с недавним официальным выпуском Swagger Core 2.0.0 все будет ускоряться.

Чтобы доказать это, Apache CXF , хорошо известная реализация JAX-RS 2.1 , является одним из первых, кто внедрил OpenAPI 3.0.0, и в сегодняшнем посте мы рассмотрим, насколько легко могли бы ваши API -интерфейсы JAX-RS 2.1. выиграть от этого.

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

1
2
3
4
POST   /api/people
GET    /api/people/{email}
GET    /api/people
DELETE /api/people/{email}

Наша модель будет состоять из одного класса Person .

1
2
3
4
5
public class Person {
    private String email;
    private String firstName;
    private String lastName;
}

Чтобы добавить немного магии, мы бы использовали Spring Boot, чтобы заставить нас работать как можно быстрее. После этого давайте начнем заполнять зависимости (при условии, что мы используем Apache Maven для управления сборкой).

1
2
3
4
5
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
    <version>3.2.4</version>
</dependency>

В последних выпусках 3.2.x Apache CXF представляет новый модуль cxf-rt-rs-service-description-openapi-v3, посвященный OpenAPI 3.0.0 , на основе Swagger Core 2.0.0 .

01
02
03
04
05
06
07
08
09
10
11
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-rs-service-description-openapi-v3</artifactId>
    <version>3.2.4</version>
</dependency>
 
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>swagger-ui</artifactId>
    <version>3.13.6</version>
</dependency>

Наличие Swagger UI не является строго обязательным, но это исключительно полезный и красивый инструмент для изучения ваших API (и, если он доступен в classpath, Apache CXF легко интегрирует его в ваши приложения, как мы скоро увидим) ,

Предпосылки на месте, давайте сделаем немного кодирования! Прежде чем мы начнем, стоит отметить, что Swagger Core 2.0.0 имеет много способов заполнить определения OpenAPI 3.0.0 для ваших сервисов, включая файлы свойств, аннотации или программно. В этом посте мы будем использовать только аннотации.

01
02
03
04
05
06
07
08
09
10
11
12
13
@OpenAPIDefinition(
    info = @Info(
        title = "People Management API",
        version = "0.0.1-SNAPSHOT",
        license = @License(
            name = "Apache 2.0 License",
            url = "http://www.apache.org/licenses/LICENSE-2.0.html"
        )
    )
)
@ApplicationPath("api")
public class JaxRsApiApplication extends Application {
}

Выглядит довольно просто, @OpenAPIDefinition устанавливает определение верхнего уровня для всех наших веб-API. Переходя к PeopleRestService , мы просто добавляем аннотацию @Tag , ну, в общем , помечаем наш API.

1
2
3
4
5
@Path( "/people" )
@Tag(name = "people")
public class PeopleRestService {
    // ...
}

Круто, пока ничего сложного. Мясная часть начинается с определений операций веб-API, поэтому давайте рассмотрим первый пример — операцию выборки для всех.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Produces(MediaType.APPLICATION_JSON)
@GET
@Operation(
    description = "List all people",
    responses = {
        @ApiResponse(
            content = @Content(
                array = @ArraySchema(schema = @Schema(implementation = Person.class))
            ),
            responseCode = "200"
        )
    }
)
public Collection<Person> getPeople() {
    // ...
}

Довольно много аннотаций, но в целом выглядит довольно чисто и понятно. Давайте посмотрим на другую, конечную точку, чтобы найти человека по его адресу электронной почты.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@Produces(MediaType.APPLICATION_JSON)
@Path("/{email}")
@GET
@Operation(
    description = "Find person by e-mail",
    responses = {
        @ApiResponse(
            content = @Content(schema = @Schema(implementation = Person.class)),
            responseCode = "200"
        ),
        @ApiResponse(
            responseCode = "404",
            description = "Person with such e-mail doesn't exists"
        )
    }
)
public Person findPerson(
        @Parameter(description = "E-Mail address to lookup for", required = true)
        @PathParam("email") final String email) {
    // ...
}

В том же духе операция по удалению человека по электронной почте выглядит в основном идентично.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Path("/{email}")
@DELETE
@Operation(
    description = "Delete existing person",
    responses = {
        @ApiResponse(
            responseCode = "204",
            description = "Person has been deleted"
        ),
        @ApiResponse(
            responseCode = "404",
            description = "Person with such e-mail doesn't exists"
        )
     }
)
public Response deletePerson(
        @Parameter(description = "E-Mail address to lookup for", required = true )
        @PathParam("email") final String email) {
    // ...
}

Отлично, давайте подведем итоги, посмотрев на последнюю, пожалуй, самую интересную конечную точку, которая добавляет нового человека (выбор использования @FormParam предназначен исключительно для иллюстрации различных вариантов API).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@POST
@Operation(
    description = "Create new person",
    responses = {
        @ApiResponse(
            content = @Content(
                schema = @Schema(implementation = Person.class),
                mediaType = MediaType.APPLICATION_JSON
            ),
            headers = @Header(name = "Location"),
            responseCode = "201"
        ),
        @ApiResponse(
            responseCode = "409",
            description = "Person with such e-mail already exists"
        )
    }
)
public Response addPerson(@Context final UriInfo uriInfo,
        @Parameter(description = "E-Mail", required = true)
        @FormParam("email") final String email,
        @Parameter(description = "First Name", required = true)
        @FormParam("firstName") final String firstName,
        @Parameter(description = "Last Name", required = true)
        @FormParam("lastName") final String lastName) {
    // ...
}

Если у вас есть опыт документирования веб-API с использованием более старых спецификаций Swagger , вы можете найти подход довольно знакомым, но более многословным (или, лучше сказать, формализованным). Это результат огромной работы, которую ведет спецификация и сообщество, чтобы сделать ее максимально полной и расширяемой.

API определены и задокументированы, пришло время опробовать их! Недостающим элементом является конфигурация Spring, где мы инициализируем и предоставляем наши веб-службы JAX-RS .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackageClasses = PeopleRestService.class)
public class AppConfig {
    @Autowired private PeopleRestService peopleRestService;
  
    @Bean(destroyMethod = "destroy")
    public Server jaxRsServer(Bus bus) {
        final JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();
 
        factory.setApplication(new JaxRsApiApplication());
        factory.setServiceBean(peopleRestService);
        factory.setProvider(new JacksonJsonProvider());
        factory.setFeatures(Arrays.asList(new OpenApiFeature()));
        factory.setBus(bus);
        factory.setAddress("/");
 
        return factory.create();
    }
 
    @Bean
    public ServletRegistrationBean cxfServlet() {
        final ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new CXFServlet(), "/api/*");
        servletRegistrationBean.setLoadOnStartup(1);
        return servletRegistrationBean;
    }
}

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

1
2
3
4
5
6
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(AppConfig.class, args);
    }
}

Давайте построим и запустим его сразу

1
2
mvn clean package
java -jar target/jax-rs-2.1-openapi-0.0.1-SNAPSHOT.jar

После запуска приложения спецификация OpenAPI 3.0.0 наших веб-API должна быть действующей и доступной для использования в формате JSON по адресу:

1
http://localhost:8080/api/openapi.json

Или в формате YAML по адресу:

1
http://localhost:8080/api/openapi.json

Разве не было бы замечательно изучить наши веб-API и поиграть с ними? Поскольку мы включили зависимость пользовательского интерфейса Swagger , это не просто, просто перейдите по адресу http: // localhost: 8080 / api / api-docs? Url = / api / openapi.json :

Особое внимание следует уделить небольшому значку Swagger UI рядом с вашей версией API, намекая на его соответствие спецификации OpenAPI 3.0.0 .

Отметим здесь, что в использовании Spring Boot нет ничего особенного. В случае, если вы используете Apache CXF внутри контейнера OSGi (например, Apache Karaf ), также доступна интеграция с OpenAPI 3.0.0 (пожалуйста, ознакомьтесь с официальной документацией и примерами, если вы заинтересованы в данной теме).

Все это выглядит легко и просто, но как насчет перехода на OpenAPI 3.0.0 со старых версий спецификаций Swagger ? Apache CXF имеет мощную функцию для преобразования старых спецификаций на лету, но в целом портал OpenApi.Tools является подходящим местом для оценки ваших возможностей.

Следует ли перейти на OpenAPI 3.0.0 ? Я искренне верю, что вы должны, по крайней мере, попытаться поэкспериментировать с этим, но, пожалуйста, имейте в виду, что инструмент все еще не достаточно зрел, ожидайте несколько препятствий на этом пути (которые вы сможете преодолеть, добавив патчи, кстати ). Но, несомненно, будущее светлое!

Полные источники проекта доступны на Github .

Опубликовано на Java Code Geeks с разрешения Андрея Редько, партнера нашей программы JCG . См. Оригинальную статью здесь: Переходя от The Times: на пути к принятию OpenAPI v3.0.0 в API JAX-RS

Мнения, высказанные участниками Java Code Geeks, являются их собственными.