Em um post anterior, falamos sobre como modelar uma API REST com o Swagger. Essa abordagem, em que pensamos nos detalhes da API antes da implementá-la, é conhecida como Contract-First ou API-First.
Uma outra abordagem, talvez mais comum, é iniciar pela implementação da API para só então documentá-la. Essa abordagem é conhecida como Contract-Last.
Vamos utilizar o Swagger para documentar uma API REST tanto para máquinas quanto para humanos.
Lembrando da API do Payfast
Documentaremos a API do Payfast, uma API de pagamentos simplificada que implementamos no curso , utilizando JAX-RS.
Autor(a)
alexandre.aquiles
Inscreva-se em nossa Newsletter
Fique por dentro de conteúdos, insights e oportunidades do universo tech. Receba novidades e lançamentos direto no seu e-mail.
Entre as classes de modelo, temos Transacao e Pagamento: ```java
public class Transacao { private String numero; private String titular; private LocalDate data; private BigDecimal valor;
Na nossa API REST, um POST em /pagamentos cria um novo pagamento a partir de uma transação. Então é possível confirmar o pagamento com um PUT ou cancelá-lo com um DELETE.
Uma implementação dessa API utilizando JAX-RS seria algo como:
@Path("/pagamentos")publicclassPagamentoResource { //... @POST @Consumes(MediaType.APPLICATION\_JSON) public Response criarPagamento(Transacao transacao) throws URISyntaxException { //aqui, código que cria pagamento a partir da transacao... return Response.created(new URI("/pagamentos/" + pagamento.getId())).entity(pagamento) .type(MediaType.APPLICATION\_JSON).build(); }@PUT@Path("/{id}")@Produces(MediaType.APPLICATION\_JSON)public Response confirmarPagamento(@PathParam("id") Integer id) { //aqui, código que confirma pagamento... return Response.ok().entity(pagamento).build(); }@DELETE@Path("/{id}")@Produces(MediaType.APPLICATION\_JSON)public Response cancelarPagamento(@PathParam("id") Integer id) { //aqui, código que cancela pagamento... return Response.ok().entity(pagamento).build(); } }
Nota: Para que a serialização de/para JSON funcione para o java.time.LocalDate da classe Transacao em um servidor Java EE 7, são necessárias algumas configurações. Fazemos isso na classe JacksonJavaTimeConfiguration.
Não podemos esquecer de habilitar o JAX-RS na nossa aplicação: ```java
@ApplicationPath("/v1") public class PagamentoService extends Application { }
# Configurando o Swagger na sua API
Para usar o Swagger para documentar nossa API, precisamos de seus jars. A melhor maneira é utilizar alguma ferramenta de gerenciamento de dependências. Para configurar a dependência com o Maven: ```xml
<dependency><groupId>io.swagger</groupId><artifactId>swagger-jaxrs</artifactId><version>1.5.7</version></dependency>
Nota: Pode ser interessante excluir algumas dependências transitivas do swagger-jaxrs.
Por meio da classe BeanConfig, do pacote io.swagger.jaxrs.config, fazemos configurações básicas como título, descrição e versão da API, endereço do servidor e contexto da aplicação, se é usado HTTP ou HTTPS e pacotes cujos recursos REST devem ser escaneados. Criamos um objeto dessa classe no construtor de PagamentoService e ao invocar o método setScan, as configurações são realizadas.
Além disso, precisamos carregar as classes ApiListingResource e SwaggerSerializers do Swagger. Para isso, sobrescrevemos o método getClasses de Application, em PagamentoService. Uma coisa chata é que, ao fazermos isso, precisamos adicionar manualmente a classe JacksonJavaTimeConfiguration e o recurso PagamentoResource.
@ApplicationPath("/v1")publicclassPagamentoServiceextendsApplication { publicPagamentoService() { //código omitido... } @Override public Set<Class<?>> getClasses() { Set<Class<?>> resources = new HashSet<>(); resources.add(JacksonJavaTimeConfiguration.class); resources.add(PagamentoResource.class); //classes do swagger... resources.add(ApiListingResource.class); resources.add(SwaggerSerializers.class); return resources; } }
Para que o Swagger gere a documentação para o nosso recurso, devemos anotar a classe PagamentoResource com @Api: ```code language="java" highlight="1"
@Api @Path("/pagamentos") public class PagamentoResource { //restante do código... }
Há algumas diferenças entre o YAML modelado para a nossa API no post anterior e o YAML gerado pelo Swagger a partir de PagamentoResource.
Para o POST em /pagamentos, as diferenças mais relevantes são:
Não há uma descrição (summary)
Sumiram os content-types recebidos e enviados
O response ficou com uma descrição genérica, sem o cabeçalho de Location nem o status 201
O parâmetro está não obrigatório e com um nome ruim (body)
Para definir uma descrição e content-types para o POST em /pagamentos, devemos utilizar a anotação @ApiOperation. Para o status e cabeçalho no response, usamos as anotações @ApiResponses, @ApiResponse e @ResponseHeader. Para modificar a obrigatoriedade e nome do parâmetro, usamos @ApiParam. ```code language="java" highlight="1-14,18-21"
@ApiOperation( value = "Cria novo pagamento", consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON) @ApiResponses( @ApiResponse( code=201, message="Novo pagamento criado", response = Pagamento.class, responseHeaders= @ResponseHeader( name="Location", description="uri do novo pagamento", response=String.class))) @POST @Consumes(MediaType.APPLICATION_JSON) public Response criarPagamento( @ApiParam( value="Transação", name="transacao", required=true) Transacao transacao) throws URISyntaxException { //código omitido... }
Após essas configurações, o trecho do POST no YAML passaria a ser gerado assim: ```code language="ruby" highlight="3,6-9,12-14,18-25"
/pagamentos: post: summary: "Cria novo pagamento" description: "" operationId: "criarPagamento" consumes: - "application/json" produces: - "application/json" parameters: - in: "body" name: "transacao" description: "Transação" required: true schema: $ref: "#/definitions/Transacao" responses: 201: description: "Novo pagamento criado" schema: $ref: "#/definitions/Pagamento" headers: Location: type: "string" description: "uri do novo pagamento"
Bem parecido com o que tinhamos antes!
As diferenças mais importantes para o PUT e o DELETE são as seguintes:
Não há o status 200 no response
O path parameter está duplicado
Podemos usar a anotação @ApiResponse para melhorar as informações do response. Infelizmente, não há como evitar a duplicação do parâmetro id.
Temos o JSON e o YAML, mas esses formatos não são legíveis para pessoas. São quase tão difíceis de ler quanto um WSDL.
Em Web Services do estilo SOAP, é bastante comum que sejam disponibilizados PDFs que focam em descrever a API para humanos. É o caso dos Correios e da Nota Fiscal Paulista, por exemplo.
Será que precisamos escrever um PDF manualmente? Seria interessante alguma maneira de transformar esse JSON ou YAML em uma página HTML com boa usabilidade.
Para isso, há o projeto Swagger UI! Na página da ferramenta, podemos baixar um zip com o último release. Depois de extrair, basta copiar o conteúdo da pasta dist para uma pasta doc no document root folder (WebContent ou webapp) do nosso projeto.
Ao acessar novamente a página, teríamos uma documentação razoavelmente legível:
O Swagger UI, através de um código JavaScript, transforma o swagger.json nessa documentação. É possível disparar requisições de teste com diferentes content-type. É possível utilizar autorização, oauth, entre várias outras funcionalidades.
//swagger-payfast.7e14.starter-us-west-2.openshiftapps.com/v1/swagger.json) e [http://localhost:8080/fj36-payfast/v1/swagger.yaml](http://swagger-payfast.7e14.starter-us-west-2.openshiftapps.com/v1/swagger.yaml)
`PagamentoResource`
https
//openapis.org/specification) para detalhar URIs, Content-Types, métodos HTTP aceitos, códigos de resposta e modelos de dados. O YAML gerado ficaria assim: ```ruby