DEV Community

Documentando uma API REST Spring Boot 3 usando OpenAPI 3.0

Visão Geral

A documentação é uma parte essencial da construção de APIs REST. Neste tutorial, veremos SpringDoc, que simplifica a geração e manutenção de documentos de API com base na especificação OpenAPI 3 para aplicativos Spring Boot 3.x.

Configuração

Image description

Spring Boot 3.x requer o uso da versão 2 do springdoc-openapi:

ext {
    springdocVersion = "2.3.0"
}

dependencies {
    implementation "org.springframework.boot:spring-boot-starter-web"
    // ...
    implementation "org.springframework.boot:spring-boot-starter-validation"
    implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:${springdocVersion}"
    implementation "org.springdoc:springdoc-openapi-starter-common:${springdocVersion}"
    // ...
    testImplementation "org.springframework.boot:spring-boot-starter-test"
}
Enter fullscreen mode Exit fullscreen mode

Depois de configurar a dependência corretamente, podemos executar nossa aplicação e encontrar as descrições da OpenAPI em /v3/api-docs, que é o caminho padrão:

http://localhost:8080/v3/api-docs
Enter fullscreen mode Exit fullscreen mode

Podemos personalizar o caminho em application.properties usando a propriedade springdoc.api-docs. Por exemplo, podemos definir o caminho para /api-docs:

springdoc.api-docs.path=/api-docs
Enter fullscreen mode Exit fullscreen mode

Então, poderemos acessar a documentação na url:

http://localhost:8080/api-docs
Enter fullscreen mode Exit fullscreen mode

As definições OpenAPI estão no formato JSON por padrão. Para o obter o formato yaml, podemos podemos acessar a url:

http://localhost:8080/api-docs.yaml
Enter fullscreen mode Exit fullscreen mode

Integração com UI Swagger

Além de gerar a especificação OpenAPI 3, podemos integrar springdoc-openapi com Swagger UI para interagir com nossa especificação de API e testar os endpoints. A dependência springdoc-openapi já inclui a UI do Swagger, então neste ponto, já podemos acessar a Swagger UI em:

http://localhost:8080/swagger-ui/index.html
Enter fullscreen mode Exit fullscreen mode

Support for swagger-ui Properties

A biblioteca springdoc-openapi também suporta propriedades swagger-ui. Elas podem ser usados ​​como propriedades Spring Boot com o prefixo springdoc.swagger-ui. Por exemplo, podemos personalizar o caminho da Swagger UI alterando a propriedade springdoc.swagger-ui.path dentro do nosso arquivo application.properties:

springdoc.swagger-ui.path=/documentation.html
Enter fullscreen mode Exit fullscreen mode

Então podemos acessar a Swagger UI na url:

http://localhost:8080/documentation.html
Enter fullscreen mode Exit fullscreen mode

API de exemplo

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Collection;

@RestController
@RequestMapping("/api/books")
public class BookController {

    private final BookRepository repository;

    public BookController(BookRepository repository) {
        this.repository = repository;
    }

    @PostMapping
    public ResponseEntity<?> create(@RequestBody final Book payload, UriComponentsBuilder ucb) {
        Book book = repository.create(payload);

        var location = ucb
                .path("/movies/{id}")
                .buildAndExpand(book.getId())
                .toUri();

        return ResponseEntity.created(location).build();
    }

    @GetMapping("/{id}")
    public Book findById(@PathVariable final Long id) {
        return repository.findById(id)
            .orElseThrow(BookNotFoundException::new);
    }

    @GetMapping("/")
    public Collection<Book> findBooks() {
        return repository.getBooks();
    }

    @PutMapping("/{id}")
    @ResponseStatus(HttpStatus.OK)
    public ResponseEntity<?> update(@PathVariable("id") final Long id, @RequestBody final Book payload) {
        if (repository.findById(id).isEmpty()) return ResponseEntity.notFound().build();

        return ResponseEntity.accepted().body(repository.update(id, payload));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> delete(@PathVariable final Long id) {
        Book book = repository.findById(id).orElseThrow(BookNotFoundException::new);
        repository.delete(book);
        return ResponseEntity.noContent().build();
    }
}
Enter fullscreen mode Exit fullscreen mode

Repositório de exemplo

import org.springframework.stereotype.Repository;
import java.util.*;

@Repository
public class BookRepository {

    private final Map<Long, Book> books = new HashMap<>();

    public Optional<Book> findById(final long id) {
        return Optional.ofNullable(books.get(id));
    }

    public Book create(Book book) {
        Long id = (long) (books.size() + 1);
        book.setId(id);
        books.put(id, book);
        return books.get(id);
    }

    public Collection<Book> getBooks() {
        return books.values();
    }

    public Book update(final long id, final Book book) {
        Book bookUpdate = books.get(id);
        bookUpdate.setAuthor(book.getAuthor());
        bookUpdate.setTitle(book.getTitle());
        books.put(id, bookUpdate);

        return books.get(id);
    }

    public void delete(final Book book) {
        books.remove(book.getId());
    }
}
Enter fullscreen mode Exit fullscreen mode

Modelo de exemplo

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public class Book {
    private long id;
    private String title;
    private String author;

    // Getters and Setters
}

class BookNotFoundException extends RuntimeException {
}
Enter fullscreen mode Exit fullscreen mode

Application Properties de exemplo


# Open Api
springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.operationsSorter=alpha
springdoc.api-docs.version=OPENAPI_3_0
spring.jpa.hibernate.ddl-auto=none

# Swagger
springdoc.swagger-ui.path=/documentation.html
Enter fullscreen mode Exit fullscreen mode

Testando

Então, se executarmos nossa API agora, podemos visualizar a documentação em:

http://localhost:8080/documentation.html
Enter fullscreen mode Exit fullscreen mode

Image description

Aqui podemos usar a Swagger UI para testar nossa API fazendo:

  • cadastro de novos livros
  • obtendo todos os livros cadastrados
  • obtendo um livro pelo id
  • atualizando um livro cadastrado
  • deletando um livro cadastrado

Faça testes e explore a Documentação gerada automáticamente.

Geração automática de documentos usando validação de bean JSR-303

Quando nosso modelo inclui anotações de validação de bean JSR-303, como @NotNull, @NotBlank, @Size, @Min e @Max, a biblioteca springdoc-openapi utiliza essas anotações para gerar documentação de esquema adicional para as restrições correspondentes. Vejamos um exemplo usando nosso bean Book:

public class Book {

    private long id;

    @NotBlank
    @Size(min = 0, max = 20)
    private String title;

    @NotBlank
    @Size(min = 0, max = 30)
    private String author;

}
Enter fullscreen mode Exit fullscreen mode

Percebe agora como ficou a documentação do Schema de Book:

Image description

Gerando documentação adicional usando @ControllerAdvice e @ResponseStatus:

Usar @ResponseStatus em métodos em uma classe @RestControllerAdvice o openapi vai gerar automaticamente documentação para os códigos de resposta. Nesta classe @RestControllerAdvice, os dois métodos são anotados com @ResponseStatus:

import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(ConversionFailedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<String> handleConversion(RuntimeException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(BookNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<String> handleBookNotFound(RuntimeException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}
Enter fullscreen mode Exit fullscreen mode

As a result, we can now see the documentation for the response codes 400 and 404:

Image description

Gerando documentação usando @Operation e @ApiResponses

A seguir, vamos ver como podemos adicionar alguma descrição à nossa API usando algumas anotações da especificação OpenAPI.

Para fazer isso, anotaremos o endpoint /api/book/{id} do nosso controlador com @Operation e @ApiResponses:

@Operation(summary = "Get a book by its id")
@ApiResponses(value = { 
  @ApiResponse(responseCode = "200", description = "Found the book", 
    content = { @Content(mediaType = "application/json", 
      schema = @Schema(implementation = Book.class)) }),
  @ApiResponse(responseCode = "400", description = "Invalid id supplied", 
    content = @Content), 
  @ApiResponse(responseCode = "404", description = "Book not found", 
    content = @Content) })
@GetMapping("/{id}")
public Book findById(@Parameter(description = "id of book to be searched") 
  @PathVariable long id) {
    return repository.findById(id).orElseThrow(() -> new BookNotFoundException());
}
Enter fullscreen mode Exit fullscreen mode

Agora nosso endpoint /api/book/{id} ficou documentado assim:

Image description

Conclusão

Neste artigo, aprendemos como configurar o springdoc-openapi em nossos projetos. Em seguida, vimos como integrar springdoc-openapi com a UI do Swagger.

Depois disso, vimos como o springdoc-openapi gera documentação automaticamente usando anotações de validação de bean JSR 303 e as anotações @ResponseStatus na classe @ControllerAdvice.

Também aprendemos como adicionar uma descrição à nossa API usando algumas anotações específicas da OpenAPI

O springdoc-openapi gera documentação da API de acordo com as especificações do OpenAPI 3. Além disso, ele também cuida da configuração da UI do Swagger para nós, tornando a geração de documentos da API uma tarefa razoavelmente simples.

Referencias

Top comments (0)