Перейти к основному содержимому

Реализация сервера Open API с использованием генератора OpenAPI

· 6 мин. чтения

1. Обзор

Как следует из названия, генератор OpenAPI генерирует код из спецификации OpenAPI . Он может создавать код для клиентских библиотек, серверных заглушек, документации и конфигурации.

Он поддерживает различные языки и фреймворки. Примечательно, что есть поддержка C++, C#, Java, PHP, Python, Ruby, Scala — почти всех широко используемых .

В этом руководстве мы узнаем , как реализовать заглушку сервера на основе Spring с помощью OpenAPI Generator через его плагин maven . Другими способами использования генератора являются его интерфейс командной строки или онлайн-инструменты .

2. YAML-файл

Для начала нам понадобится файл YAML с указанием API. Мы передадим его в качестве входных данных для нашего генератора для создания серверной заглушки.

Вот фрагмент нашего файла petstore.yml :

openapi: "3.0.0"
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
...
responses:
...
post:
summary: Create a pet
operationId: createPets
...
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
...
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string

3. Зависимости Maven

3.1. Плагин для генератора OpenAPI

Далее добавим зависимость Maven для плагина генератора:

<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>5.1.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>
${project.basedir}/src/main/resources/petstore.yml
</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>com.foreach.openapi.api</apiPackage>
<modelPackage>com.foreach.openapi.model</modelPackage>
<supportingFilesToGenerate>
ApiUtil.java
</supportingFilesToGenerate>
<configOptions>
<delegatePattern>true</delegatePattern>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>

Как мы видим, мы передали файл YAML как inputSpec . После этого, так как нам нужен сервер на базе Spring, мы использовали имя генератора как spring .

Затем apiPackage указывает имя пакета, в котором будет сгенерирован API. Далее у нас есть modelPackage , куда генератор помещает модели данных. Когда для параметра delegatePattern установлено значение true , мы просим создать интерфейс, который можно реализовать как настраиваемый класс @Service .

Важно отметить, что параметры генератора OpenAPI одинаковы независимо от того, используете ли вы интерфейс командной строки, подключаемые модули Maven/Gradle или параметры онлайн-генерации .

3.2. Зависимости Maven

Поскольку мы будем генерировать сервер Spring, нам также нужны его зависимости ( Spring Boot Starter Web и Spring Data JPA ), чтобы сгенерированный код компилировался и выполнялся должным образом :

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.4.6</version>
</dependency>
</dependencies>

Помимо указанных выше зависимостей Spring, нам также понадобятся зависимости jackson -databind и swagger2 , чтобы наш сгенерированный код успешно скомпилировался:

<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>

4. Генерация кода

Чтобы сгенерировать заглушку сервера, нам просто нужно запустить:

mvn clean install

В результате получаем:

./f14de196bd0e73e9b66747c032e07adc.png

Теперь давайте посмотрим на код, начиная с содержимого apiPackage .

Во- первых, мы получаем интерфейс API под названием PetsApi , который содержит все сопоставления запросов, как определено в спецификации YAML. Вот фрагмент:

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
@Validated
@Api(value = "pets", description = "the pets API")
public interface PetsApi {
/**
* GET /pets : List all pets
*
* @param limit How many items to return at one time (max 100) (optional)
* @return A paged array of pets (status code 200)
* or unexpected error (status code 200)
*/
@ApiOperation(value = "List all pets", nickname = "listPets", notes = "",
response = Pet.class, responseContainer = "List", tags={ "pets", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "A paged array of pets",
response = Pet.class, responseContainer = "List"),
@ApiResponse(code = 200, message = "unexpected error", response = Error.class) })
@GetMapping(value = "/pets", produces = { "application/json" })
default ResponseEntity<List> listPets(@ApiParam(
value = "How many items to return at one time (max 100)")
@Valid @RequestParam(value = "limit", required = false) Integer limit) {
return getDelegate().listPets(limit);
}

// other generated methods
}

Во-вторых, поскольку мы используем шаблон делегата, OpenAPI также генерирует для нас интерфейс делегатора PetsApiDelegate . В частности, методы, объявленные в этом интерфейсе, по умолчанию возвращают HTTP-статус 501 Not Implemented :

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
public interface PetsApiDelegate {
/**
* GET /pets : List all pets
*
* @param limit How many items to return at one time (max 100) (optional)
* @return A paged array of pets (status code 200)
* or unexpected error (status code 200)
* @see PetsApi#listPets
*/
default ResponseEntity<List<Pet>> listPets(Integer limit) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"name\" : \"name\", \"id\" : 0, \"tag\" : \"tag\" }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}

// other generated method declarations
}

После этого мы видим класс PetsApiController , который просто подключается к `` делегату :

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
@Controller
@RequestMapping("${openapi.swaggerPetstore.base-path:}")
public class PetsApiController implements PetsApi {

private final PetsApiDelegate delegate;

public PetsApiController(
@org.springframework.beans.factory.annotation.Autowired(required = false) PetsApiDelegate delegate) {
this.delegate = Optional.ofNullable(delegate).orElse(new PetsApiDelegate() {});
}

@Override
public PetsApiDelegate getDelegate() {
return delegate;
}
}

В modelPackage генерируется пара POJO модели данных, называемых Error и Pet , на основе схем , определенных во входных данных YAML.

Давайте посмотрим на один из — Pet :

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
public class Pet {
@JsonProperty("id")
private Long id;

@JsonProperty("name")
private String name;

@JsonProperty("tag")
private String tag;

// constructor

@ApiModelProperty(required = true, value = "")
@NotNull
public Long getId() {
return id;
}

// other getters and setters

// equals, hashcode, and toString methods
}

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

Теперь все, что требуется для того, чтобы заглушка сервера функционировала как сервер, — это добавить реализацию интерфейса делегатора.

Для простоты здесь мы этого делать не будем, а только протестируем заглушку. Более того, перед этим нам понадобится приложение Spring :

@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

5.1. Тест с использованием завитка

После запуска приложения мы просто запустим команду:

curl -I http://localhost:8080/pets/

И вот ожидаемый результат:

HTTP/1.1 501 
Content-Length: 0
Date: Fri, 26 Mar 2021 17:29:25 GMT
Connection: close

5.2. Интеграционные тесты

В качестве альтернативы мы можем написать простой интеграционный тест для того же самого:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class OpenApiPetsIntegrationTest {
private static final String PETS_PATH = "/pets/";

@Autowired
private MockMvc mockMvc;

@Test
public void whenReadAll_thenStatusIsNotImplemented() throws Exception {
this.mockMvc.perform(get(PETS_PATH)).andExpect(status().isNotImplemented());
}

@Test
public void whenReadOne_thenStatusIsNotImplemented() throws Exception {
this.mockMvc.perform(get(PETS_PATH + 1)).andExpect(status().isNotImplemented());
}
}

6. Заключение

В этом руководстве мы увидели, как сгенерировать заглушку сервера на основе Spring из спецификации YAML с помощью плагина maven генератора OpenAPI .

В качестве следующего шага мы также можем использовать его для создания клиента .

Как всегда, исходный код доступен на GitHub .