3

API REST usando Spring Boot 2, Hibernate, JPA, e H2 com Deploy no Heroku.

#Java #REST #Spring Boot
Yvson Ferreira
Yvson Ferreira

API REST usando Spring Boot 2, Hibernate, JPA, e H2 com Deploy no Heroku.

Por Yvson Ferreira.


O Problema que Queremos Resolver:


Precisamos implementar uma API REST que precisa suportar o processo de abertura de nova conta no banco. O primeiro passo desse fluxo é cadastrar os dados pessoais de uma

pessoa. Precisamos de apenas algumas informações obrigatórias:


  • Nome
  • E-mail
  • CPF
  • Data de nascimento


Caso os dados estejam corretos, é necessário gravar essas informações no banco de dados relacional e retornar o status adequado para a aplicação cliente, que pode ser uma página web ou um aplicativo mobile.

A API devolverá a resposta adequada para o caso de falha de validação.


Tecnologias a Serem Utilizadas:


As stacks principais serão Java, Spring e Hibernate e desdobrando-as usaremos:


  • Java 11;
  • Maven 3.6.3;
  • Spring Boot (última versão estável);
  • GIT/GITHUB para versionamento de código;
  • Heroku para deploy na nuvem;
  • Intellij (IDE);


Quais classes serão criadas nesse processo?


Como o problema inicial é realizar o cadastro de pessoas e como estamos usando o paradigma de Orientação a Objetos (OO) com Java, precisamos representar as pessoas cadastradas através de uma classe, e seus atributos serão as propriedades que identificam essa pessoa, assim teremos a classe Person. Essa classe representa a tabela que será gerada no banco de dados. As demais classes e interfaces serão classes que darão suporte ao cadastro de pessoas e permitirão a dinâmica da arquitetura Rest no Spring, bem como a implementação de alguns padrões de desenvolvimento, seus respectivos propósitos serão dados à medida que forem implementadas.




Qual foi o seu processo de decisão para realizar a implementação?


O primeiro passo é entender o problema e em seguida verificar o que está sendo feito para resolver problemas semelhantes, neste caso, como nosso problema está bem delimitado fica mais fácil encontrar a solução, foi pedido uma API REST para o processo de cadastro, então é necessário entender o que é uma API REST (Representational State Transfer).

Na arquitetura REST, descrita por Roy Fieldman em sua dissertação de doutorado, utilizamos o protocolo HTTP como forma de comunicação, assim clientes diferentes atingem os mesmos End Points, executam ações iguais e recebem as mesmas respostas. Esta é uma arquitetura madura e muito utilizada o que facilita a obtenção de informações. Neste padrão devemos mapear nossas operações necessárias ao cadastro e manipulação dos dados para verbos HTTP, e o framework Spring já é totalmente compatível com esta arquitetura o que facilita todo o processo de desenvolvimento. Desta forma seguiremos as recomendações da comunidade utilizando os recursos que o Spring nos oferece. 

Em seguida vamos apresentar o passo a passo para este desenvolvimento e detalhá-lo, este passo a passo reflete o processo de tomada de decisão e ajuda a responder progressivamente a pergunta: Qual o papel de cada tecnologia envolvida no projeto?


Etapa I - Configurações iniciais:


Precisamos preparar o ambiente de desenvolvimento e existem várias formas de fazer isso, escolhemos usar o Sdkman, uma ferramenta de gerenciamento de SDK (Software Development Kits) que agiliza muito este processo. Seguindo as recomendações da documentação para o uso no Linux cobrimos os seguintes passos:


Passo 1 -Instalação:


curl -s "https://get.sdkman.io" | bash

Passo 2 - Verificação do sdk


sdk version

Passo 3 - Verificando lista do java


-sdk list java | less



Passo 4 - Escolhemos uma versão e instalamos, no nosso caso escolhemos a 11.0.9


  • sdk install java 11.0.9.open-adpt


Após instalado a versão escolhida é setada como padrão, mas podemos usar o comando java --version para conferir.


É importante ressaltar que Java é uma das linguagem de programação multiplataforma e orientada a objetos e uma das mais usadas no mundo, sua história começou desde os anos 1991 na Sun Microsystems e posteriormente vendida a Oracle. Java é também uma plataforma onde rodam outras linguagens de programação como Groovy e Scala, a versão 8 do JDK teve uma grande trajetória, hoje temos até a versão 15+, porém muitos projetos na versão 8 estão sendo migrados para as versões mais recentes (ver Java SE).


Passo  5 - Instalar o maven


O Apache  Maven é um gerenciador de dependências, como muitas outras linguagens, para o desenvolvedor não ter que desenvolver absolutamente tudo que precisa ele faz uso de bibliotecas ou frameworks de terceiros, que por sua vez podem precisar de outras sub bibliotecas e assim por diante, então o Maven surge para reduzir o caos que seria buscar, baixar, instalar e gerenciar manualmente tudo isso. Através do arquivo POM.xml para configuração do Maven que expõem quais são as dependências do projeto (ver Para que serve o Maven). E para listarmos as versões disponíveis usamos o comando: sdk list maven.

Observe que a quantidade de versões é bem menor que o java porque o Maven só possui um desenvolvedor. Escolhemos a versão 3.6.3 e seguimos com o comando

sdk install maven 3.6.3

Após a instalação definimos o Maven para uso com o comando sdk use maven 3.6.3, se bem que o sdkman já seta automaticamente o Mavem para uso. Se quisermos verificar digitamos: mvn --version


Passo 6 - Instalar o Intellij. 


O Intellij é uma ferramenta para auxílio no desenvolvimento de código, uma das mais utilizadas pela comunidade desenvolvedores Java, pois oferece inúmeros recursos para as etapas de desenvolvimento (ver Intellij)

Acessamos o https://www.jetbrains.com/pt-br/idea/ e seguimos as recomendações.

Vamos usar a versão Community que é gratuita e atende perfeitamente nossas necessidades: https://www.jetbrains.com/pt-br/idea/download/#section=linux

Estou usando o linux, mas pode ser baixado para os principais S.O.s.

Se estiver usando o linux pode usar o comando:


sudo snap install intellij-idea-community --classic

Passo 7 - Heroku

Para o deploy usaremos o Heroku (ver https://www.heroku.com/), que é uma plataforma na nuvem que faz deploy de várias aplicações back-end seja para hospedagem, testes em produção ou escalar as suas aplicações. Também tem integração com o GitHub, deixando o uso mais fácil e com containers denominados Dyno. (Ver: Heroku)

É importante fazer uma conta gratuita se quiser seguir esse passo, é bem simples e rápido.


Passo  8 Github:


O Github é uma plataforma de hospedagem de código-fonte com controle de versão Git, por isso é necessário uma conta, e a instalação do git em sua máquina, são passos simples e rápidos.


Feitas as preparações iniciais podemos começar a meter a mão na massa:


ETAPA II - Configurando o Spring-Boot


O Spring Boot surgiu para resolver um grande problema do Spring, que era o tedioso processo de configuração inicial do framework, assim conseguimos em pouco tempo ter um projeto inicial com tudo que precisamos. Vamos aos passos:


Passo 1

Acessar o https://start.spring.io/ para definir as configurações iniciais do projeto.

As dependências que escolhemos foram:


Dev Tools - Permite fazer atualizações em tempo dinâmico;

Lombok - Para reduzir a digitação de códigos repetitivos (boilerplate, como gets e sets) através de anotações;

Spring WEB - Segundo o site NetBeans: “O Spring Framework é um framework de aplicação de código-fonte aberto popular que pode facilitar o desenvolvimento do Java EE. Ele consiste em um contêiner, um framework para gerenciar componentes, e um conjunto de serviços de snap-in para interfaces de usuário, transações e persistência da Web. Uma parte do Spring Framework é o Spring Web MVC, um framework MVC extensível para criação de aplicações Web”. Essa dependência já inclui integração com a arquitetura Rest, e usa o Apache Tomcat como padrão;

Spring Data JPA  - Permite a persistência dos dados em SQl usando a API Java de persistência e usando o Hibernate como padrão;

Spring Boot Actuator - Suporta endpoints integrados (ou personalizados) que permitem monitorar e gerenciar seu aplicativo - como integridade do aplicativo, métricas, sessões, etc;

H2 - É o nosso banco de dados em memória que suporta endpoints integrados (ou personalizados) que permitem monitorar e gerenciar seu aplicativo - como integridade do aplicativo, métricas, sessões, etc;


Após escolhermos nossas dependências basta clicar em generate e já temos nosso projeto inicial pronto para download. Após a descompactação, abrimos o projeto no Intellij.


Passo 2 - Configurando o Intellij.


Vamos no menu File => Project Structure navegamos até SDK para incluir nossa máquina virtual java, como tínhamos no passo anterior setado o java 11 como corrente, vamos usar essa versão, para isso é só clicar no + no lado direito que a IDE localiza os JDKs instalados:



Depois vamos em Project na coluna a esquerda e setamos o SDK 11 conforme figura abaixo:


Agora na tela inicial da IDE vamos para ADD Configuration na parte superior esquerda da tela.


Clicamos no botão + e escolhemos o template application:



Vamos agora definir a classe principal do projeto, que no caso fica em scr=>main=>java


No fim o objetivo é escolher a classe Main, a classe que está anotada com @SpringBootApplication que será a porta de entrada da aplicação. Essa será a classe que iniciará o projeto quando dermos um build.


Terminadas as configurações vamos começar a codificação:


Etapa III - Codificação:


Dentro do package vamos criar uma pasta controller e uma classe chamada PersonController.


Vamos implementar nosso primeiro controller, que é a classe responsável por receber as requisições do usuário.



Para testar podemos usar shift+F10 e rodar nossa aplicação, no browser digitamos http://localhost:8080/api/v1/people 

Agora vamos entender o código: 

A primeira anotação @RestController significa que nossa classe é um controlador que vai ser acessada por uma API Rest, a próxima anotação: @RequestMapping("/api/v1/people") informa qual o caminho principal de acesso a nossa API.

Já em @GetMapping informamos que nosso método é do tipo get, que é acessado por padrão quando informamos o endereço conforme acima, e o mesmo retorna a string “API WORK”.

“Anotação é um recurso do Java que permite inserir metadados em relação a nossa classe, atributos e métodos. Essas anotações depois poderão ser lidas por frameworks e bibliotecas, para que eles tomem decisões baseadas nessas pequenas configurações.” (ver Hibernate)

Então com tudo funcionando vamos fazer nosso primeiro deploy no Heroku


Para isso vamos criar o arquivo system.properties na raiz no projeto com a instrução: java.runtime.version=11

Essa instrução serve para informar ao Heroku a versão do java usada, pois por padrão ele usa a versão 8 e para aceitar versões superiores temos que fazer essa configuração.


Depois criamos um repositório no github para ser usado como método de deploy e subimos nosso projeto inicial.


Feito isso entramos na nossa conta no Heroku vamos adicionar um novo app conforme abaixo,escolhemos um nome válido e não usado.


Não vamos escolher um pipeline pois não é nosso objetivo, e vamos usar o GitHub como método de deployment. Informamos o repositório criado anteriormente e clicamos em connect.


Devemos habilitar a opção de fazer deploy automático e no primeiro deploy clicamos em deploy branch. Dando tudo certo veremos a tela abaixo:



Se clicarmos em View vamos para nosso endereço do Heroku, colocamos nossa URL e teremos o mesmo resultado só que agora na nuvem:


Início da Persistência dos Dados:


Agora que já temos um projeto inicial funcional podemos ir mais a fundo no nosso problema principal que consiste no cadastro de pessoas. Como falamos anteriormente vamos criar a classe Person e as demais que nos permitam cadastrar e manipular as informaçẽs das pessoas cadastradas. 

Vamos começar criando a pasta entity no nosso Package principal e nela vamos colocar a classe Person.



Vamos entender as anotações feitas na nossa classe.


Quando estávamos fazendo as configurações iniciais do projeto, incluímos uma dependência chamada Lombok, o Lombok serve para nos dar agilidade no desenvolvimento, pois nesse processo muitos códigos repetitivos são gerados em tempo de execução, por exemplo os métodos getters e setters para nos dar acesso às propriedades. E é exatamente isso que o @Data faz, ele vai fazer a inserção automática desses métodos que são requeridos pelo Hibernate que é uma é uma ferramenta para mapeamento objeto-relacional (ORM) open source, sendo uma especificação para a Java Persistence API (JPA). “O Hibernate abstrai o seu código SQL, toda a camada JDBC e o SQL será gerado em tempo de execução. Mais que isso, ele vai gerar o SQL que serve para um determinado banco de dados, já que cada banco fala um ‘dialeto’ diferente dessa linguagem. Assim há também a possibilidade de trocar de banco de dados sem ter de alterar código Java, já que isso fica como responsabilidade da ferramenta”. Como usaremos tambeḿ o JPA abstraímos mais ainda e podemos desenvolver sem nos preocuparmos com detalhes do Hibernate, assim, ele pode ser trocado por outra implementação como o EclipseLink por exemplo, sem termos que fazer alterações no nosso código. (Ver Hibernate).

Já o  @Builder vai dar um padrão de projeto para a construção de objetos e os    @AllArgsConstructor @NoArgsConstructor vai lidar com a inserção dos construtores. Desta forma nosso código fica mais enxuto e de fácil manutenção.

A anotação @Entity  serve para informar ao banco de dados que nossa classe será mapeada pelo JPA como uma entidade, e como toda entidade precisa de uma identificação temos a anotação @Id que informa qual campo será mapeado como a chave primária, ainda neste sentido usamos a @GeneratedValue(strategy = GenerationType.IDENTITY) para que os valores desse campo sejam gerados automaticamente pelo banco de dados. 

Na descrição do nosso problema informava que todos os campos eram obrigatórios, por isso colocamos @Column(nullable = false) que forçará o banco a entender esses valores como not null, ou seja serão campos obrigatórios e que se não preenchido retornarão um exceção durante o cadastro. Os campos CPF e Email costumam ser campos de valor único e então usamos a anotação @Column(nullable = false, unique = true), assim garantimos que somente um CPF e um Email seja cadastrado por pessoa. Desta forma criamos todas as restrições necessárias para o mapeamento da nossa classe para o banco de dados através do JPA em nosso projeto.


Usaremos o H2 como nosso banco de dados, o H2 é um banco de dados muito rápido, de código aberto e que nos permite gravar nossos dados em memória. 

Vamos fazer uma pequena configuração para informar a string de conexão do H2, na pasta resources no arquivo application.properties:

# H2

spring.h2.console.enabled=true


# Datasource

spring.datasource.url=jdbc:h2:mem:testdb


Podemos acessar o H2 pelo navegador de maneira muito simples.

Vamos startar novamente nossa aplicação e acessar o H2 pelo navegador digitando http://localhost:8080/h2-console/ e em JDBC URL se a string de conexão estiver diferente da que definimos, podemos inserir novamente. Depois clicamos em Connect

Em seguida veremos que nossa classe Person foi mapeada corretamente para a tabela através do JPA.



Agora com o banco funcionando vamos codificar as funcionalidades para manipular os dados.

Começaremos pela parte de criação de pessoas. E para isso vamos usar o padrão DAO (Data Access Object) através do Repository que provê uma interface para abstrair o acesso a dados encapsulando o acesso aos dados, de forma que as demais classes não precisam saber sobre isso.

Então criamos a pasta repository e nela adicionamos a interface PersonRepository que vai extender a interface JpaRepository, assim o JPA fará a maior parte do trabalho pesado como abrir e fechar as conexões com o banco de dados e as operações de CRUD, o Spring Data JPA utiliza o Hibernate por padrão e de forma transparente garante essas operações.



Depois, por injeção de dependência, adicionamos nosso repositório ao controller e acrescentamos o método para criação de pessoas.



A injeção de dependência no Spring é facilitada pela anotação @Autowired que permitirá ao Spring tratar todo o ciclo de vida (Instanciar, utilizar e destruir) toda vez que o Service for instanciado.

A anotação @PostMapping permitirá o acesso ao nosso método através do verbo Post do Http, e a anotação @RequestBody informa que as informações virão de uma requisição Http e a mesma será do tipo Person neste caso. Logo depois de receber os dados, tratamos toda a parte de persistência no banco simplesmente com uma linha de código: personRepository.save(person). Assim o nosso repositório usando os poderes do JPA fazem a persistência dos dados, em seguida retornamos uma mensagem através do  Builder da classe MessageResponseDTO.



Veja que a classe possui uma propriedade e duas anotações do Lombok, é justamente o Lombok que lida com o trabalho de criar uma classe com todos os métodos necessários para acesso a propriedade e neste caso permitir o retorno da nossa mensagem. Assim tudo que precisamos para a criação da pessoa está pronto. 

Vamos usar o Postman para testar manualmente se já conseguimos criar as pessoas e verificar nossa mensagem de retorno. 



No Postman na coluna da esquerda simulamos uma requisição Post com o corpo em Json com os dados requeridos pela classe Person, ao enviarmos a requisição tivermos o retorno esperado na coluna da direita, informando o ID da pessoa criada, observe que não informamos o Id, ele foi gerado automaticamente atraveś da anotação @GeneratedValue(strategy = GenerationType.IDENTITY) que fizemos anteriormente.

E podemos confirmar que os dados foram registrados no banco.


Se tentarmos cadastrar uma nova pessoa com os mesmos dados de CPF e Email veremos que acontecerá um erro, isso porque definimos anteriormente que esses campos não poderiam ser duplicados. Também teremos erros se não informarmos algum campo pois colocamos todos como obrigatórios.



Nosso código já está funcionando, já conseguimos cadastrar pessoas e garantir que nossas restrições sejam atendidas, porém podemos refatorar o mesmo para garantir uma testabilidade e manutenção mais fáceis.


Vamos criar uma classe de serviço para lidar com as regras de negócio e garantir que os controllers fiquem mais limpos.



E o PersonControlller ficou como exibido abaixo após a refatoração. Veja que acrescentamos o @ResponseStatus(HttpStatus.CREATED) ao método para que o status 201 seja retornado caso a pessoa seja criada com sucesso, o que está mais de acordo ao padrão Rest.



Observe que toda lógica de criação foi transferida para a classe de serviço, assim  a testabilidade fica mais fácil pois ao testar o serviço não nos preocupamos com a camada Rest e ao testarmos os controllers não nos preocupamos com o serviço. Ou seja, atribuímos as responsabilidades às classes devidas, isso se aproxima do primeiro princípio do conhecido padrão de projeto chamado S.O.L.I.D. O primeiro princípio - Princípio da responsabilidade única - (Single Responsibility Principle) declara que uma classe deve ser especializada em um único assunto e possuir apenas uma responsabilidade dentro do software, ou seja, a classe deve ter uma única tarefa ou ação para executar. A classe PersonController agora tem a responsabilidade devida de um controller, ou seja, ser um intermediário entre as camadas de visualização e a camada de dados.


Nosso código já está ficando bom, mas pode melhorar mais, porém antes de seguirmos vamos subir as modificações para o Github e verificar o deploy no Heroku.



Para verificar o funcionamento no Heroku basta modificar a URL no Postman e enviar o Post, observe que tivemos o retorno com sucesso e com o código 201. 


Seguindo nossa refatoração, vamos implementar a classe PersonDTO, a camada DTO (Data Transfer Object) é “um padrão de projetos bastante usado em Java para o transporte de dados entre diferentes componentes de um sistema, diferentes instâncias ou processos de um sistema distribuído ou diferentes sistemas via serialização”, pois “muitas vezes os dados usados na comunicação não refletem exatamente os atributos do seu modelo. Então, um DTO seria uma classe que provê exatamente aquilo que é necessário para um determinado processo”  (stackoverflow).

No nosso caso vamos aperfeiçoar nossa validação de dados para que a validação seja feita antes da comunicação com o banco de dados, assim podemos tratar as mensagens de forma mais personalizada e na camada de controller . E para isso vamos usar o Java Bean Validation.


Acrescentei as dependências abaixo ao arquivo pom.xml, assim podemos usar as anotações necessárias.

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-validation</artifactId>

</dependency>


 


A classe PersonDTO ficou como mostrado acima. A anotação @NotEmpty e @NotNull torna obrigatório o preenchimento dos campos que as contém. Com o @Size podemos definir o valor mínimo e máximo dos campos, uma anotação bem interessante é a @CPF que faz a validação para o CPF conforme o número de dígitos e etc. Assim o sistema só aceitará CPFs válidos. E o @Email valida os emails. Já o @Past no campo birthDate só aceita datas no passado, ou seja não poderemos inserir datas no futuro o que seria uma data inválida para a data de nascimento o o @JsonFormat está provendo a serialização e deserialização bem como passando o formato de datas que usamos no Brasil. Por fim, no controller colocamos o @Valid para fazer a validação assim que os dados entrarem na camada do Controller.

O método createPerson foi modificado:

@PostMapping

@ResponseStatus(HttpStatus.CREATED)

public MessageResponseDTO createPerson(@RequestBody @Valid PersonDTO personDTO){

   return personService.createPerson(personDTO);

}

Observe que agora estamos recebendo um PersonDTO em vez de um objeto do tipo Person, e estamos usando o personService para lidar com as operações de banco.

E finalmente temos a classe PersonService.



Estamos fazendo o mapeamento de PersonDTO para Person dentro da classe Service, é comum criamos Mappers para isso, existe vários caminhos possíveis, o que escolhemos foi usar um framework chamado MapStruct, para utilizá-lo colocamos as seguintes configurações no POM.xml


<dependency>

  <groupId>org.mapstruct</groupId>

  <artifactId>mapstruct</artifactId>

  <version>1.3.1.Final</version>

</dependency>

e

<annotationProcessorPaths>

  ….

  <path>

     <groupId>org.mapstruct</groupId>

     <artifactId>mapstruct-processor</artifactId>

     <version>1.3.1.Final</version>

  </path>

</annotationProcessorPaths>


Em seguida criamos a interface PersonMapper:


Em PersonService foi acrescentado:


private final PersonMapper personMapper = PersonMapper.INSTANCE;


e o método createPerson foi atualizado:


public MessageResponseDTO createPerson(PersonDTO personDTO){

       Person personToSave = personMapper.toModel(personDTO);

       Person savedPerson = personRepository.save(personToSave);

       return MessageResponseDTO

               .builder()

               .message("Created Person whit ID " + savedPerson.getId())

               .build();

}

 

Agora que já terminamos a inclusão vamos trabalhar na exibição das pessoas cadastradas, mas antes fazermos o commit das alterações para atualizarmos nosso repositório.

Já codificamos muito e apesar de parecer que estamos adicionando complexidade ao código com tantos passos extras, na realidade estamos facilitando, por exemplo para fazer a exibição de todas as pessoas cadastradas foi necessário apenas acrescentar um método no controller:


@GetMapping

public List<PersonDTO> listAll(){

   return personService.listAll();

}


E um método na classe Service:


public List<PersonDTO> listAll() {

   List<Person> allPeople = personRepository.findAll();

   return allPeople.stream()

           .map(personMapper::toDTO)

           .collect(Collectors.toList());

}

Este método usa o repository para resgatar uma lista de pessoas e usa o Mapper para os mapeamentos entre a entidade e o DTO.

Fazendo o teste com o Postman temos o retorno esperado:



Podemos ver que o retorno está em formato json de acordo com a arquitetura REST.

Agora atualizamos nosso repositório no Github e seguimos em frente criando os métodos para retorno de uma pessoa específica.


No controller acrescentamos o método findById:


@GetMapping("/{id}")

public PersonDTO findById(@PathVariable Long id){

   return personService.findById(id);

}


Começamos observando a anotação @GetMapping(“/{id}”), essa adição do id acrescenta o id a rota, e juntamente com o @PathVariable podemos recuperar a pessoa correta seguindo o padrão REST.

Continuando vamos a implementação do método na classe PersonService:


public PersonDTO findById(Long id) throws PersonNotFoundException {

   Person person = personRepository.findById(id)

               .orElseThrow(() ->new PersonNotFoundException(id));

       return personMapper.toDTO(person);

}


O findById vai procurar uma pessoa com o id informado e caso não encontre vai levantar uma exceção, que caso aconteça, dentro do orElseThrow chamamos um objeto através de uma nova classe criada para retornar uma mensagem de exceção. Porém se o id for encontrado retornamos a pessoa mapeada para o DTO.


Verificando no postman primeiro um caso de sucesso onde encontramos a pessoa:



E depois testando com um id não cadastrado:


A mensagem que escrevemos foi retornada conforme previsto.

Tudo certo, então comitamos nosso código e seguimos em frente implementando o Delete. Ou seja, vamos excluir os registros que desejarmos do nosso banco. Então primeiro criamos nosso método no controller.

@DeleteMapping("/{id}")

@ResponseStatus(HttpStatus.NO_CONTENT)

public void deleteById(@PathVariable Long id) throws PersonNotFoundException {

   personService.deleteById(id);

}

Usamos a anotação @DeleteMapping para mapear o verbo http Delete e receber o id e pedimos para o service fazer o restante, este método é do tipo void porque não possui retorno, o usuário só vai ter uma resposta de status 204 através da anotação @ResponseStatus(HttpStatus.NO_CONTENT).


Esta operação tem muitas semelhanças com o findById, pois precisaremos receber um id, verificar se ele existe e só então executar nossa ação, assim para evitar repetição de código vamos criar um método para essa verificação na classe de serviço, o verifyIfExists, depois refatoramos o método findById e implementamos o deleteById.



Fazendo o teste pelo Postman temos o status 204 No Content



Observe que modificamos a requisição HTTP no Postman para DELETE e a URI é a mesma do teste anterior. E se tentarmos excluir novamente essa pessoa teremos Not Found porque ela não existe mais na nossa base de dados.



Até aqui tudo certo, então vamos atualizar o repositório no Github e seguir para próxima operação. É importante atualizarmos nosso repositório a cada operação pois assim mantemos um histórico do nosso desenvolvimento e em caso de introduzirmos algum erro no futuro podemos voltar até um ponto mais estável do nosso código.


Agora só falta o PUT para termos uma API Rest básica que contempla as operações mais importantes da arquitetura Rest e resolver nosso problema inicial. O PUT é o verbo Http utilizado para atualizações, para atualizarmos nosso cadastro de pessoas precisamos localizar a pessoa pelo id e depois receber os dados atualizados e válidos e então salvá-los novamente no banco, por fim damos o retorno ao usuário, já implementamos essas funcionalidade nos diversos métodos anteriores, observe que o updateById será bem parecido com o savePerson, inclusive a mensagem de retorno também será parecida, por isso vamos fazer algumas melhorias na classe PersonService para finalizarmos nossa API.


Primeiro criamos o método no controller:


@PutMapping("/{id}")

public MessageResponseDTO updateById(@PathVariable Long id,

                                    @RequestBody @Valid PersonDTO personDTO)

                                   throws PersonNotFoundException {

   return personService.updateById(id, personDTO);

}


Agora usamos a anotação @PutMapping para mapear o verbo Put, recebemos o id e as informações para a atualização já validadas e passamos para nosso ServicePerson esperando o retorno das operações.


Anteriormente quando criamos a classe PersonDTO faltou incluir o Id que agora será necessário para que ao tentarmos salvar a pessoa com um Id existente o Spring entenda que ela já existe e então apenas atualizar as informações. Assim incluímos o private Long id; em PersonDTO. Após as modificações em ServicePerson temos:


O novo método createMessageResponse que vai retornar a mensagem recebida e o id da pessoa.


private MessageResponseDTO createMessageResponse(Long id, String message) {

   return MessageResponseDTO

           .builder()

           .message(message + id)

           .build();

}


O método createPerson atualizado com a chamada ao createMessageResponse :


public MessageResponseDTO createPerson(PersonDTO personDTO){

       Person personToSave = personMapper.toModel(personDTO);

       Person savedPerson = personRepository.save(personToSave);


   return createMessageResponse(savedPerson.getId(), "Created person with ID ");

}


E o novo método updateById:


public MessageResponseDTO updateById(Long id, PersonDTO personDTO) throws PersonNotFoundException {

   verifyIfExists(id);


   Person personToUpdate = personMapper.toModel(personDTO);


   Person updatedPerson = personRepository.save(personToUpdate);

   return createMessageResponse(updatedPerson.getId(), "Updated person with ID ");

}

Esse método recebe o id e os dados da pessoa verifica se a pessoa existe utilizando o verifyIfExists, faz a conversão de personDto para person com o nosso personMapper e em seguida salva as operações no banco com o personRepository, por fim retornamos nossa mensagem de sucesso usando o createMessageResponse. Observe que todos esses tratamentos são desconhecidos do controller que apenas enviou os dados e espera o retorno da operação. Se a pessoa não existir, teremos uma mensagem de  exceção pelo PersonNotFoundException.


Testando pelo Postman colocamos  a requisição PUT e na URI informamos o id, também informamos o id no corpo da requisição, e como ela existia recebemos a mensagem e o status de sucesso.





Se tentarmos atualizar uma pessoa não existente teremos a resposta apropriada com o status 404.




Pronto, temos tudo funcionando conforme precisávamos, vamos apenas fazer uma pequena melhoria que é acrescentar o trecho @AllArgsConstructor(onConstructor = @__(@Autowired)) sobre as nossas classes PersonController e PersonService, e depois removemos os construtores delas, assim o próprio Lombok vai lidar com a injeção de dependência via construtor de forma automática, assim não importa quantas dependências teremos, elas já serão injetadas por ele.


Agora podemos atualizar o repositório e testar nossa API no Heroku.


Uma etapa importante no desenvolvimento é a etapa de testes, testes unitários e testes de integração são executados de forma automática para garantir a qualidade do código, porém faremos apenas os testes manuais que também ajudam a verificar o funcionamento do sistema.


Etapa IV - Testes


Cadastrando uma pessoa com os dados válido, recebemos o status 201:


Tentando cadastrar um pessoa com dados inválidos recebemos o status 400:


Listando as pessoas cadastradas:


Buscando uma pessoa cadastrada pelo Id:



Tentando buscar um usuário inexistente, observe que o retorno do erro é mais amigável e recebemos a mensagem que personalizamos:





Atualizando uma pessoa com dados válidos:


Tentando atualizar uma pessoa com dados inválidos, neste caso colocamos um CPF inválido na atualização:


Nossa validação do CPF entra em ação e não permite cadastrar um CPF inválido, note também que o objeto retornado é do tipo personDTO, esse é um dos motivos de usar essa camada extra, ela acaba blindando nossas entidades de acessos exteriores.


Tentando atualizar uma pessoa inexistente:



Excluindo uma pessoa existente:



Excluindo uma pessoa inexistente:



Com esses testes manuais, cobrimos uma boa parte das funcionalidades de nossa API e confirmamos seu correto funcionamento na hospedagem.


Conclusão:


Neste artigo vimos como solucionar um problema do mundo real utilizando tecnologias do universo Java dentre outras, e seguindo padrões de projetos que estão em alta no mundo do desenvolvimento. Dessa forma temos uma API REST funcional e escalável já rodando na nuvem através do Heroku, é claro que para ser totalmente funcional devemos usar um banco de dados melhor, o que pode ser feito sem alterar quase nada do código, basicamente teríamos que informar os detalhes da nova conexão no arquivo application.properties. Outro passo importante é a inclusão de testes automáticos. 

Abaixo estão as referências principais que foram utilizadas para dar base ao artigo e que podem ser usadas como material para aprofundamento nas tecnologias utilizadas.


Os cógigos usados neste artigo se encontram em : https://github.com/yvpaulo/personapi 


Agradecimentos:

Agradeço ao instrutor Rodrigo Peleias, esse artigo foi baseado em sua aula referenciada a baixo.


Referências:


DTO: https://pt.stackoverflow.com/questions/31362/o-que-%C3%A9-um-dto 

H2: https://www.h2database.com/html/main.html 

Hibernate: https://www.caelum.com.br/apostila-java-web/uma-introducao-pratica-ao-jpa-com-hibernate 

Ivan Souza, O que é Rest : https://rockcontent.com/br/blog/rest/ 

IntelliJ: https://www.jetbrains.com/pt-br/idea/ 

Java SE: https://www.oracle.com/java/technologies/java-se-glance.html 

Loiane Groner, CRUD REST using Spring Boot 2, Hibernate, JPA, and MySQL em:

https://www.oracle.com/br/technical-resources/articles/dsl/crud-rest-sb2-hibernate.html 

Lombok: https://github.com/rzwitserloot/lombok/wiki 

MapStruct: https://mapstruct.org/ e https://medium.com/dev-cave/mapstruct-mapeando-seus-dtos-para-model-8bc362b628fe 

Maven: https://pt.stackoverflow.com/questions/59240/para-que-serve-o-maven 

Padrão DAO: 

https://pt.stackoverflow.com/questions/113840/como-funciona-o-padr%C3%A3o-dao 

Postman: https://learning.postman.com/docs/getting-started/introduction/ 

REST: https://restfulapi.net/ 

Rodrigo Peleias : Spring Boot - API REST para Cadastro de usuários.https://github.com/rpeleias/personapi_dio_live_coding 

Roy Fielding: https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm 

Spring Boot: https://spring.io/projects/spring-boot 

Spring Data JPA : https://spring.io/projects/spring-data-jpa 


0
116

Comentários (0)

Biografia

Brasil