DEV Community

Fabiano Góes • e-Programar
Fabiano Góes • e-Programar

Posted on

15

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

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more