Construindo uma API RESTful com Java e Spring Framework — Parte 1

Mariana Azevedo
12 min readSep 11, 2019

Hoje em dia, está cada vez mais comum termos aplicações que funcionam online, em navegadores ou dispositivos móveis. Essas aplicações tem por objetivo consumir informação por meio de interfaces que implementam uma série rotinas e padrões que chamamos de API.

O acrônimo API vem da expressão em inglês Application Programming Interface (em português, Interface de Programação de Aplicações). Uma API é um conjunto de padrões e regras documentadas para que uma aplicação X possa utilizar funcionalidades de uma aplicação Y sem precisar conhecer os detalhes da implementação dessa aplicação X.

Ainda está obscuro? Para facilitar o entendimento, vamos imaginar o dia-a-dia de uma empresa que possui um e-commerce. Os desenvolvedores que trabalham na solução tem como objetivo criar a dinâmica da loja, como criar, atualizar, deletar os produtos internamente e mostrar os produtos para os clientes. Essas funcionalidades podem ser criadas em uma aplicação do lado do servidor como se fosse uma API, de forma que o site do e-commerce possa usar essas informações.

Agora que já sabemos o que é uma API e para que utilizamos uma, precisamos entender os protocolos que são utilizadas para a comunicação entre as aplicações e como os dados trafegados podem ser representados.

HTTP, REST e representações de dados em APIs

HTTP e REST são a mesma coisa?

O principal protocolo de comunicação na Web é o HTTP. Ele funciona como um protocolo de requisição-resposta em um modelo que chamamos de cliente-servidor. No exemplo acima, do e-commerce, o navegador que é usado para acessar o site seria o cliente e o computador ou máquina virtual em algum serviço de cloud em que a API está hospedada é o servidor. Assim, o cliente manda uma requisição HTTP para o servidor e o servidor, com recursos e conteúdos próprios, retorna uma mensagem de resposta para o cliente.

Exemplo do funcionamento do protocolo HTTP

O protocolo HTTP tem sido usado desde 1990 e a versão atual do protocolo é o HTTP/3. O protocolo define oito métodos que determinam ações a serem efetuadas no momento da requisição de algum recurso ao servidor. Desses oito, os 4 mais utilizados são:

  • GET: método utilizado para ler e recuperar dados. Requisita uma representação do recurso especificado e retorna essa representação.
  • POST: método utilizado para criar um novo recurso. Envia dados ao servidor. O tipo do corpo da solicitação é indicado pelo cabeçalho Content-Type.
  • PUT: cria um novo recurso ou substitui uma representação do recurso de destino com os novos dados. A diferença entre PUT e POST é que PUT é idempotente: ao chamá-lo uma ou várias vezes sucessivamente o efeito é o mesmo, enquanto se chamar o POST repetidamente pode ter efeitos adicionais. Por exemplo, se criarmos um produto com POST, se a URL definida na API for chamada 20 vezes, 20 produtos serão criados e cada um deles terá um ID diferente. Já o com o PUT se você executar a URL definida na API 20 vezes, o resultado tem que ser o mesmo: o mesmo produto atualizado 20 vezes.
  • DELETE: exclui o recurso.

Baseado nesses métodos , o servidor deve processar cada uma das requisições e retornar uma resposta adequada. O conteúdo da resposta pode estar no formato XML, JSON, YAML, texto, dentre outros. E essas as respostas são separadas em cinco grupos:

  • 1XX — Informações Gerais
  • 2XX — Sucesso
  • 3XX — Redirecionamento
  • 4XX — Erro no cliente
  • 5XX — Erro no servidor

Mas isso não é REST? Não, não é. REST, acrônimo de Representational State Transfer, é uma abstração dessa arquitetura que detalhamos acima. É um estilo de arquitetura de software que define uma série de restrições para a criação de web services (serviços Web), ou seja, restringe como seus componentes devem interagir entre si. Esse termo foi introduzido e definido por Roy Fielding em sua tese de doutorado no final dos anos 90 e início dos anos 2000.

Na tese, Fielding definiu os princípios REST que eram conhecidos como “modelo de objeto HTTP” e passaram a ser utilizados ​​no projeto dos padrões HTTP 1.1 e URI (Uniform Resource Identifiers). Dessa forma, podemos dizer que em sua semântica, o REST utiliza-se métodos HTTP. Além desse conceito, vale lembrar também que um serviço REST deve ser Stateless: toda requisição deve ser autossuficiente, ou seja, cada requisição é um requisição diferente e independente. Não deve existir na requisição nenhuma forma de guardar o estado de uma informação.

E o que é ser RESTful? Dizemos que uma API é RESTful, se garantimos que implementação da API está de acordo com essa arquitetura REST explicada acima. Conceitualmente, nos serviços RESTful, tanto os dados quanto as funcionalidades são considerados recursos e ficam acessíveis aos clientes através da utilização de URIs. Essas URI’s normalmente são endereços na web que identificam tanto o servidor no qual a aplicação está hospedada quanto a própria aplicação e qual dos recursos oferecidos pela mesma está sendo solicitado.

Dessa forma, expor as funcionalidades da sua API (ou funcionalidades dos seus serviços, se você preferir) no modo RESTful, os princípios REST e suas restrições se aplicam a você também. Com todos os conceitos na mesa, vamos colocar a mão na massa e construir uma API? Iremos utilizar a linguagem Java (versão 11) com Spring Framework como base da API. Além disso, usaremos como ferramentas:

  • Apache Maven (para gestão de dependências)
  • Postman (para execução de testes e requisições em geral na API)
  • JUnit5 (para testes unitários e de integração)
  • Lombok (para reduzir código boilerplate)
  • Log4j (para adicionar logs na aplicação)
  • TravisCI (para integração contínua)

Todo o código produzido no artigo, em uma versão simplificada da API, pode ser acessada no repositório abaixo:

Caso você queira pular o tutorial e ver o código final, que é uma versão otimizada aplicando boas práticas, você pode acessar o repositório no Github:

Construindo nossa API

Primeiros passos com a API RESTful

A API deve criar, atualizar, deletar e listar viagens. Além disso, deve calcular estatísticas sobre as os tickets de viagem criados. Nesse exemplo, não vamos tratar camada de persistência, e, por isso, de forma ilustrativa, a entidade Travel conterá um campo chamado id. A API terá os seguintes endpoints:

POST/api-travels/travels: cria uma viagem.

Body da requisição:

{   
"id": 1,
"orderNumber": "220788",
"amount": "22.88",
"startDate": "2019–09–11T09:59:51.312Z",
"type": "ONE-WAY"
}

Em que:

  • id: número único da transação;
  • orderNumber: número de identificação de uma viagem no sistema interno.
  • amount: valor da viagem; deve ser uma String de tamanho arbitrário que pode ser parseada como um BigDecimal;
  • startDate: data de início da viagem no formato ISO 8601 YYYY-MM-DDThh:mm:ss.sssZ no timezone local.
  • endDate: data de fim da viagem no formato ISO 8601 YYYY-MM-DDThh:mm:ss.sssZ no timezone local. Pode ser nulo se a viagem é só de ida.
  • type: se a viagem é apenas de ida (ONE-WAY), ida e volta (RETURN) ou é composta de múltiplos destinos (MULTI-CITY).

Deve retornar com body vazio com um dos códigos a seguir:

  • 201: em caso de sucesso.
  • 400: caso o JSON seja inválido.
  • 422: se qualquer um dos campos não for parseável ou se a data de início da viagem é mais recente que a data de fim (para todos os casos, exceto viagens apenas de ida).

PUT/api-travels/travels/{id}: atualiza uma viagem.

Body da requisição:

{   
"id": 1,
"orderNumber": "220788",
"amount": "30.06",
"startDate": "2019–09–11T09:59:51.312Z",
"type": "ONE-WAY"
}

Deve ser enviado o objeto que será modificado. O retorno deve ser o próprio objeto modificado.

{   
"id": 1,
"orderNumber": "220788",
"amount": "30.06",
"startDate": "2019–09–11T09:59:51.312Z",
"type": "ONE-WAY"
}

A resposta deve conter os códigos a seguir:

  • 200: em caso de sucesso.
  • 400: caso o JSON seja inválido.
  • 404: caso tentem atualizar um registro que não existe.
  • 422: se qualquer um dos campos não for parseável (JSON mal formatado).

GET/api-travels/travels: retorna todas as viagens criadas.

Deve retornar uma lista de viagens.

{   
"id": 1,
"orderNumber": "220788",
"amount": "30.06",
"startDate": "2019–09–11T09:59:51.312Z",
"type": "ONE-WAY"
},
{
"id": 2,
"nsu": "300691",
"amount": "120.0",
"startDate": "2019–09–11T10:22:30.312Z",
"type": "ONE-WAY"
}

A resposta deve conter os códigos a seguir:

  • 200: caso exista viagens criadas.
  • 404: caso não exista viagens criadas.

DELETE/api-travels/travels: remove todas as viagens.

Deve aceitar uma requisição com body vazio e retornar 204.

GET/api-travels/statistics: retorna estatísticas básicas sobre as viagens criadas.

{   
"sum": "150.06",
"avg": "75.3",
"max": "120.0",
"min": "30.06",
"count": "2"
}

Em que:

  • sum: um BigDecimal especificando a soma total das viagens criadas.
  • avg: um BigDecimal especificando a média dos valores das viagens criadas.
  • max: um BigDecimal especificando o maior valor dentre as viagens criadas.
  • min: um BigDecimal especificando o menor valor dentre as viagens criadas.
  • count: um long especificando o número total de viagens.

Todos os campos que são BigDecimal devem ter apenas duas casas decimais, por exemplo: 15.385 deve ser retornado como 15.39.

Detalhadas as funcionalidades que precisamos implementar, mãos à obra!

1) Primeiro passo é criar um projeto Spring Boot no Spring Initializr.

Exemplo de criação de projeto Spring Boot pelo Spring Initializr

Como dependências, vamos selecionar o Spring Web (Spring MVC) e Lombok. O Spring MVC é um framework que ajuda no desenvolvimento de aplicações web no padrão MVC (model-view-controller). O Lombok é uma biblioteca Java focada em produtividade e redução de código por meio de anotações que ensinam o compilador a criar e manipular código Java. Ou seja, você não vai mais precisar escrever métodos getter, setter, equals, hashCode, construtores de classe, etc.

2) Com o projeto criado, vamos criar os pacotes model, controller e service.

Exemplo da criação dos pacotes do projeto

Em seguida, criaremos as models: Travel e Statistic.

Exemplo da classe Travel do pacote model
Exemplo da classe Statistic do pacote model

3) Com as models criadas, criaremos as services com as funcionalidades da API: TravelService e StatisticService.

A classe TravelService deve conter sete métodos para atender os requisitos do ciclo de vida desse serviço na API: isJSONValid (para verificar se o JSON é válido), parseId (para parsear o campo id do JSON), parseAmount (para parsear o campo amount do JSON), parseStartDate (para parsear o campo startDate do JSON), parseEndDate (para parsear o campo endDate do JSON), isStartDateGreaterThanEndDate (para verificar se a data de início da viagem é anterior a data de fim da viagem), create (para criar uma viagem), update (atualizar uma viagem), add (para adicionar uma viagem na lista), findById (para recuperar uma viagem por id), find (para recuperar todas as viagens criadas) e delete (remover viagens).

Exemplo da classe do pacote service TravelService

Já a classe StatisticService deve conter apenas o método para criar estatísticas da API: createStatistics.

Exemplo da classe do pacote service StatisticsService

4) Com as funcionalidades criadas, faremos agora as rotas nas controllers: TravelController (para as rotas relacionadas às viagens) e StatisticController (para as rotas relacionadas às estatísticas).

Na classe TravelController, implementaremos as operações detalhadas no início da seção: criar uma viagem (POST), atualizar uma viagem (PUT), listar todas as viagens (GET) e remover todas as viagens (DELETE).

Exemplo da controller das rotas do CRUD de viagens

Criadas as rotas, vamos subir a API com o comando:

mvn spring-boot:run

Também é possível executar a API pela sua IDE como Java Application. O resultado de ambos comandos deve ser semelhante à figura abaixo.

Spring Boot inicializando a aplicação

Por default, a API estará disponível no endereço: http://localhost:8080/. Com a API funcionando já podemos testar o funcionamento das rotas. Para os testes iniciais, utilizei o Postman e como referência os exemplos do enunciado da seção Construindo nossa API.

POST/api-travels/travels via Postman

Exemplo de um POST feito via Postman

PUT/api-travels/travels/1 via Postman

Exemplo de um PUT feito via Postman

GET/api-travels/travels via Postman

Exemplo de um GET feito via Postman

DELETE/api-travels/travels via Postman

Exemplo de um DELETE feito via Postman

Na classe StatisticController, implementaremos a operação e cálculo das estatísticas detalhada no início da seção.

Exemplo da controller da rota de estatística

GET/api-travels/statistics via Postman

Exemplo de um GET na rota de estatísticas, feito via Postman

Executando testes unitários e de integração

Como testar sua API de forma simples e rápida

Com as funcionalidades da API implementadas, precisamos testá-las. Para automatizar os testes da aplicação utilizei o JUnit5. Para que ambos os testes (unitário e integração) sejam executados corretamente (nos goals corretos do Maven), precisamos adicionar alguns plugins no nosso pom.xml: maven-failsafe-plugin e build-helper-maven-plugin. A configuração deve ser a mesma abaixo:

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<trimStackTrace>false</trimStackTrace>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId
<executions>
<execution>
<id>add-integration-test-sources</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/it/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-integration-test-resources</id>
<phase>generate-test-resources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/it/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>

Conforme já estruturado na configuração, criaremos dois pacotes: src/test/java, de teste unitário, que irá conter métodos para validar a lógica implementada nas services e controllers; e src/it/java para classe TravelsJavaApiIntegrationTest, de teste de integração para validar integração e fluxo das rotas na API. Para os testes de integração, utilizaremos o TestRestTemplate do próprio Spring Boot.

Exemplo da classe de teste unitário TravelsJavaApiUnitTests
Exemplo da classe de teste de integração FinancialJavaApiIntegrationTest

Caso você não tenha familiarização com JUnit, leia esse artigo. Para executar somente os testes unitários use o comando:

mvn test

Para executar os testes unitários e de integração, basta usar o comando:

mvn integration-test

O resultado deve ser semelhante ao da figura abaixo:

Exemplo da execução dos testes com Maven

Com o fluxo de teste pronto, o último passo é criar o fluxo de integração contínua: fazer com que a evolução da API seja monitorada constantemente, mantendo a integração frequente do código que foi e será desenvolvido. Usaremos como ferramenta o TravisCI. Para mais detalhes da prática e do TravisCI, leia esse artigo. Na próxima seção, faremos a configuração final.

Configurando Integração Contínua com TravisCI

Construir sempre para conquistar sempre!

O primeiro passo para habilitar o fluxo da integração contínua é criar um arquivo .travis.yml, na raiz do projeto, com a seguinte configuração:

Nessa configuração, estabelecemos que o build será feito em uma distribuição Linux mais atual, com a linguagem Java, sem permissão de admin e com o JDK 11. Com a máquina virtual configurada, o TravisCI irá fazer o download do código em até 2 commits, executar a instalação do pacote e em seguida os testes.

Em seguida, é necessário habilitar o fluxo da API no dashboard do TravisCI:

Habilitando fluxo da API

Caso a configuração esteja correta, assim que um commit for realizado no projeto, um build da branch principal é disparado automaticamente.

Trigger do build da branch master

O resultado esperado é que o build seja bem sucedido.

Build finalizado com sucesso

Ao analisar com detalhes o log do build, podemos ver que a branch foi construída e os testes executados com sucesso.

Log do build no TravisCI

Pronto, nossa API está pronta e validada! O assunto abordado aqui foi de caráter introdutório. Caso tenha propostas de melhoria, é só dar um fork no projeto travels-api no Github e codar!

Se quiser validar algumas boas práticas recomendadas no desenvolvimento de uma API (que será assunto para um outro post), como API versioning, HATEOAS, tratamentos de erros com Spring, dê uma olhada no projeto travels-java-api. Caso tenha outras dúvidas, é só entrar em contato comigo.

Espero que tenham gostado do post. Abraços!

Referências

  1. HTTP — Hypertext Transfer Protocol (2019): https://pt.wikipedia.org/wiki/Hypertext_Transfer_Protocol
  2. REST (2019): https://pt.wikipedia.org/wiki/REST
  3. O que é API? REST e RESTful? Conheça as definições e diferenças! https://becode.com.br/o-que-e-api-rest-e-restful/
  4. Uma rápida introdução ao REST (2008): https://www.infoq.com/br/articles/rest-introduction/
  5. Serviços RESTful: verbos HTTP (2019): https://www.devmedia.com.br/servicos-restful-verbos-http/37103
  6. Status Code Definition (2019): https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
  7. Qualidade e JUnit: introduzindo automatização de testes unitários do seu software Java no dia-a-dia (2018): https://bit.ly/2m5nOqR.
  8. Utilizando Travis CI como servidor de integração contínua em projetos open-source: um exemplo com a linguagem Java (2018): https://bit.ly/2kDpI1F

--

--

Mariana Azevedo

Senior Software Developer/Tech Lead, master in Computer Science/Software Engineering, Java, open source, and software quality enthusiast.