Implementando uma CRUD API no Spring Boot com Kotlin — parte 2

Alex Felipe
CollabCode
Published in
11 min readOct 9, 2017

No artigo anterior, criamos uma API REST capaz de criar e listar notas. Entretanto, a nossa proposta é criar uma CRUD API, ou seja, precisamos também adicionar as funcionalidades de alterar e remover as notas.

Sendo assim, neste artigo veremos como podemos tanto alterar como remover um recurso!

Quer aprender mais sobre Kotlin tanto no mundo mobile como no back-end? Então confira este agregador de conteúdo onde listo todos os conteúdos que escrevi de Kotlin e os que serão publicados mais pra frente 😉

Para começar a brincadeira, vamos criar a função alter() dentro do nosso NoteController que vai ficar responsável em receber requisições capazes de alterar uma nota:

Adicionando função alter

Tanto na função list() como também na função add() adicionamos mapeamentos do protocolo HTTP que indicam uma ação, por exemplo, para requisições do tipo GET pegamos nossos recursos e no tipo POST adicionamos um recurso…

Em outras palavras, como podemos indicar que queremos alterar um recurso considerando a arquitetura REST?

Mapeando a função para alterar um recurso

No HTTP, indicamos que queremos alterar um recurso por meio de requisições do tipo PUT. Sendo assim, vamos adicionar a annotation @PutMapping na função alter():

Adicionando a annotation @PutMapping na função alter

Da mesma forma como fizemos na função add(), precisamos também de um objeto do tipo Note que vai ser recebido no corpo da requisição:

Recebendo objeto do tipo Note via corpo da requisição

Até o momento não temos muita novidade ao que vimos anteriormente, porém, já que pretendemos alterar algo que exista na API, precisamos de alguns pontos de atenção, sendo assim, vamos entendê-los.

Entendendo a técnica para alterar recursos do REST

Em uma API REST, quando pretendemos operar com um recurso que exista, precisamos, primeiramente de uma informação que identifique o recurso.

Isso significa que além de enviar o objeto JSON com as informações que queremos alterar, precisamos também indicar, de forma explícita, um valor que identifique o recurso que queremos alterar.

No nosso caso, a maneira pela qual podemos identificar o nosso recurso Note é por meio do seu id, concorda?

“Legal, mas como podemos indicar para a requisição que queremos alterar um recurso de id específico?”

Uma das maneiras comuns para enviarmos tal informação é por meio do caminho da URL, por exemplo, já que acessamos nossos recursos com o caminho /notes, poderíamos enviar ids da seguinte maneira:

  • /notes/1
  • /notes/2
  • /notes/3

Dessa forma, temos a capacidade de realizar requisições com a intenção de alterar um recurso especificando o seu id.

Entretanto, como podemos mapear esse tipo de caminho no nosso Controller?

Adicionando parâmetros no mapeamento

Todas as annotations de mapeamento do Spring recebem uma String via parâmetro para indicar sob qual URL elas irão atender, por exemplo, se chegarmos na @PutMapping e adicionarmos o valor "id":

Colocando o mapeamento da função alter com valor fixo

Significa que a nossa função alter() será chamada em uma requisição HTTP do tipo PUT com o seguinte valor http://localhost:8080/notes/id, ou seja, temos a capacidade de ajustar a forma pela qual uma função de um Controller é chamada.

Entretanto, perceba que dessa forma, fazemos com que o valor id seja considerado valor fixo que não muda! Sendo que a nossa ideia é fazer com que, nessa parte da URL, consigamos enviar valores variáveis… E agora?

Mapeando caminhos variáveis

Além de apenas declarar caminhos fixos, os mapeamentos do Spring também nos permite adicionar variáveis!

Isso significa que podemos indicar que a String "id" é uma variável adicionando chaves:

Adicionando valor variável na URL da função alter

Com essa configuração, a requisição será do tipo PUT com o caminho /notes, porém, temos a capacidade de mandar um valor qualquer logo em seguida!

Mas ainda tem um detalhe importante, pois da maneira como está configurado agora, em nenhum momento estamos pegando o valor variável do caminho…

Recebendo variáveis a partir da URL

Da mesma maneira como fizemos para receber a nossa nota via corpo da requisição, podemos também receber as variáveis da URL pedindo por um parâmetro que a representa, como por exemplo, um id do mesmo tipo do id da classe Note, nesse caso, o tipo Long:

Adicionando id do tipo Long na função alter

Entretanto, repara que da mesma maneira como identificamos o objeto do tipo Note como corpo da requisição, precisamos indicar que o parâmetro id faz parte de uma variável da URL. Para isso utilizamos a annotation @PathVariable:

Adicionando a annotation @PathVariable no parâmetro id

Pronto, configuramos o nosso mapeamento, mas repara que ainda não estamos fazendo nada com os parâmetros que estamos recebendo…

Em outras palavras, temos que alterar o objeto que tenha o mesmo id que enviamos.

Buscando uma nota pelo seu id

Considerando que iremos alterar um recurso que exista no nosso banco de dados, a princípio precisamos garantir se o mesmo existe, ou seja, temos que pedir para o Spring Data nos indicar se, a partir do id recebido, temos uma nota salva.

Para isso, podemos utilizar a função exists() do nosso noteResository enviando o id:

Chamando a função exists do repository

A partir da função exists() o Spring Data retorna true caso exista e false caso contrário. Portanto, podemos adicionar um if para tomarmos uma ação caso existir uma nota com o id enviado:

Adicionando um if para realizar um ação caso a nota exista

Com a garantia da existência da nota, precisamos apenas salvar a nota que recebemos com a função save() da mesma forma como fizemos na função add():

Salvando a nota caso a mesma exista no banco de dados

Nesse momento você deve estar pensando:

“Mas a função save() não é uma função para salvar objetos novos?"

Além de apenas salvar objetos novos, a função save() também altera um objeto que já exista! Ou seja, se tiver um objeto com o mesmo id no banco ele já faz esse serviço pra gente.

Conseguimos alterar o objeto! Porém, para verificar se realmente a nota será alterada, vamos retornar a função save() que devolve o objeto que foi alterado:

Adicionando retorno na função alter

Repara que além de retornar o objeto que foi salvo, estamos retornando uma instância vazia, pois dessa forma, quando não alterarmos uma nota, indicaremos que nada foi alterado com o objeto vazio, ou seja, com os valores padrões.

Pronto, implementamos! Agora vamos testar \o/

Testando a requisição PUT

Para realizar o teste, primeiro precisamos verificar quais notas temos. Atualmente tenho as seguintes notas quando faço uma requisição do tipo GET:

Requisição GET para pegar as notas da API

Repara que tenho tanto a nota 1 como também a 2, sendo assim, vou alterar a nota 1:

Requisição PUT para alterar uma nota de id 1

Veja que agora conseguimos alterar a nossa nota, mas surgiu um detalhe engraçado…

Faz sentido enviarmos o id dentro do objeto da nota?

Se já enviamos o id via variável na URL, significa que iremos alterar a nota que contenha esse id, concorda? Inclusive, esse tipo de comportamento abre possibilidades para falhas comuns, como por exemplo, esse tipo de requisição:

Requisição PUT para nota de id 1, porém com o objeto JSON sem id e retornando nota de id 3

Veja que a nossa aplicação apresentou um comportamento um tanto quanto estranho, isto é, pedimos para alterar uma nota de id 1 e foi salva uma nota nova de id 3…

“Por que isso aconteceu?”

Cuidados ao implementar API REST

Se observamos novamente o nosso código:

Verificando o código atualmente

Atualmente estamos evitando a situação na qual tentamos alterar uma nota de id que não exista e podemos até ver isso no seguinte teste:

Tentando alterar nota de id 4, porém o retorno é um objeto com os parâmetro default

Repara que tentamos alterar uma nota de id 4 e retorna uma resposta 200, mas, devolve um objeto com os valores padrões, inclusive, se buscarmos todas as notas:

Buscando todas as notas com requisição GET

Veja que só aparece as 3 notas que foram salvas.

“Então por que naquela situação deu problema?“

O detalhe é que quando enviamos um objeto sem id, o Hibernate interpreta como um objeto novo, ou seja, ele vai salvar da mesma maneira como fazemos na função add(). Nesse momento você deve estar pensando,

“Então é só tomarmos cuidado quando a nota não tiver um id?”

Não somente isso, pois também pode existir esse outro caso:

Alterando uma nota de id 3 com id no objeto JSON 10 e retornando uma nota de id 4

Veja que dessa vez tentamos alterar uma nota de id 3 via variável na URL, mas enviamos um id que ainda não foi persistido no objeto JSON, como é o caso do 10, mas foi salvo uma nota de id 4.

Ações para defender a API REST

Se analisarmos num contexto geral, não faz sentido o cliente ter a possibilidade de manipular a informação de id dos recursos, pois quando queremos alterar um objeto, o id do mesmo, será recebido via variável da URL e, nos casos em que salvarmos uma nota nova, o servidor vai ficar responsável em gerar o id.

Impedindo a deserialização do id

Já que estamos utilizando o Jackson, podemos bloquear a deserialização do id com a annotation @JsonProperty enviando os parâmetros value = “id" e access = JsonProperty.Access.READ_ONLY:

Bloqueando a deserialização do campo id com a annotation @JsonProperty

Desta maneira, evitamos as seguintes situações não desejadas:

  • Quando alteramos uma nota com um id que existe, mas o objeto enviado via JSON possui um id diferente que compromete um recurso existente ou cria um novo
  • Quando criamos um objeto novo e o objeto JSON está com um id que exista, então ele altera um recurso existente

Entretanto, ainda estamos tentando alterar uma nota de um objeto que não tem id, ou seja, quando entramos dentro do if precisamos fazer com que a nota que vai ser salva, contenha o mesmo id recebido via variável da URL.

Criando um objeto com as informações desejadas

Uma das técnicas que podemos fazer é criar uma nova instância adicionando as informações do id e do objeto note recebido via parâmetro da função alter():

Criando um objeto seguro a partir do objeto recebido pela requisição

Veja que criamos o objeto safeNote que contém as informação que esperamos, ou seja, estamos protegendo a nossa API com uma nota segura.

Mas já que estamos no Kotlin, será que existe uma possibilidade mais objetiva?

Ao invés de ficar criando uma nova instância manualmente cada vez que vier um objeto, temos a capacidade de utilizar uma feature do Kotlin capaz de nos ajudar nessa tarefa.

Conhecendo a feature Data Classes

Além de declararmos classes como vimos até agora, no Kotlin temos a capacidade de declarar Data Classes. Para declarar uma Data Class, basta apenas colocar o prefixo a keyword data:

Transformando a classe de notas em Data Class

De modo geral, o objetivo deste tipo de classe (existem outros tipos hehe) é armazenar dados, porém, além disso, ela também já implementa algumas funções padrões baseando-se nas properties que foram declaradas via construtor primário:

  • equals()/hashCode(): para comparar objetos;
  • toString(): com o seguinte padrão"Note(id=valorDoId, title=valorDoTítulo, description=descriçãoDaNota)";
  • componentN(): indicar a ordem das properties quando utilizamos o recurso de Destructuring Declaration;
  • copy(): realiza a cópia do objeto.

Existem regras e outros detalhes importantes sobre Data Classes, mas, é um assunto para outro momento. Entretanto, fique à vontade em perguntar sobre o assunto ou consultar a documentação.

Dentre as funções disponíveis, a que pode nos ajudar é justamente a copy(), considerando o nome da função, já sabemos que ela nos permite copiar um objeto.

Entretanto, ela também permite modificar o valor de uma property durante a cópia, como por exemplo, no momento que criamos a nossa safeNote:

Copiando o objeto recebido no corpo da requisição com id diferente

Estamos fazendo novamente uma cópia do objeto recebido com o id desejado, porém, de uma maneira muito mais objetiva! Vamos testar?

Testando novamente a API

Primeiro começaremos com o fluxo normal, enviando o id apenas via variável da URL:

Requisição do tipo PUT para um recurso de id 3 sem especificar o id no objeto JSON

Até o momento nenhuma novidade. Nosso segundo teste é verificar o que acontece quando enviamos um id diferente quando tentamos alterar um recurso:

Requisição PUT para alterar recurso de id 3 porém com o id 10 no objeto JSON enviado no corpo da requisição

Uhul! O primeiro caso problemático já era 😄

Mas, e quando tentamos enviar um id que já exista?

Requisição do tipo PUT para alterar recurso de id 1 enviando um objeto JSON com id 3

Veja que pedimos para alterar uma nota de id 1, mas enviamos uma nota de id 3 no corpo do objeto JSON, que por sinal existe no banco de dados, porém, a nossa API foi capaz de evitar esse tipo de problema!

Agora o último caso problemático que só comentamos mas não testamos, é quando tentamos criar uma nova nota enviando um id no objeto JSON de um recurso que já existe.

Para testar esse comportamento basta apenas realizar uma requisição POST:

Requisição do tipo POST enviando um objeto JSON com id

Veja que agora deixamos a nossa API segura! Mas ainda precisamos finalizar a nossa proposta inicial que é criar uma CRUD API, portanto, vamos começar a com a implementação da removação de notas.

Implementando a função para remover recursos

Para essa feature precisamos fazer algo similar ao que fizemos na função alter(), ou seja, primeiro vamos declarar a função delete():

Declarando a função delete

Mapeando função para remover um recurso

Em seguida, vamos mapear essa função para que indique ao HTTP que desejamos realizar uma remoção no recurso. Para isso utilizamos o verbo DELETE, nem preciso falar que vamos utilizar a annotation @DeleteMapping, né?

Adicionando a annotation @DeleteMapping na função delete

Estamos com a função declarada, porém, como implementamos uma função que remove um recurso?

Basicamente precisamos apenas do id do recurso, portanto, vamos receber esse id da mesma maneira como fizemos na função alter():

Recebendo variável da URL na função delete

Então, da mesma maneira, verificamos se a nota existe:

Verificando a existência do recurso pelo id na função delete

E então, basta apenas pedir para o noteRepository remover com a função delete() enviando o id via parâmetro:

Removendo nota a partir do id com a função delete

Para testar, basta apenas realizar uma requisição do tipo DELETE, no meu caso tenho notas com id do 1 a 5, vou remover a nota de id 2:

Requisição do tipo DELETE para remover nota de id 2

Repara que dessa vez não precisamos enviar nada via corpo da requisição, como também, recebemos como resposta 200 sendo que não retornamos nada na função delete()… Vamos pegar todas as notas e ver se a de id 2 foi removida:

Buscando todas as notas após requisição de remoção com requisição do tipo GET

Veja que funcionou! Legal, mas agora, pra finalizar, vamos tentar remover uma nota de id que não existe:

Requisição do tipo DELETE com id 10

Veja que temos a mesma resposta 200, agora vamos pegar todas as notas novamente pra garantir se está tudo certo:

Buscando novamente todas as notas após segunda remoção com requisição do tipo GET

Nada aconteceu, ou melhor, finalizamos a nossa CRUD API REST! \o/

Código fonte

Caso surgir alguma dúvida, fique à vontade em consultar o código fonte que deixei no repositório no GitHub.

Para saber mais

Por mais que tenhamos finalizados a implementação da CRUD API na arquitetura REST, ainda existem pontos que vale considerar durante a implementação, como por exemplo:

  • Utilização da camada Service para ficar entre o Controller e o Repository, pois, dessa forma, evitamos que o Controller lide com responsabilidade que não é dele, como é o caso de realizar comunicação direta com o Repository ou qualquer outra entidade.
  • Retornar a classeHttpEntity ao invés do objeto direto para que seja possível se comunicar de maneira adequada com os clientes que consumirem a API.

Claro, ainda existem muitos outros detalhes que valem a pena aprender dentro do ecossistema do Spring Framework, como por exemplo, o projeto Spring Data REST ou o Spring Security.

Conclusão

Neste artigo aprendemos a implementar as funcionalidades tanto para alterar como para remover um recurso dentro de uma API REST.

Vimos que nesse tipo de implementação é muito comum realizarmos operações em recursos que existam, ou seja, quando alteramos ou removemos um recurso, primeiro precisamos garantir se o mesmo existe para depois tomarmos uma ação.

Além disso, aprendemos uma feature nova do Kotlin, que é justamente o Data Class que além de armazenar as informações da classe, implementa algumas funções que são úteis no nosso dia a dia, como por exemplo o copy() que nos ajudou no processo manipular um objeto mantendo as informações desejadas.

Antes que eu esqueça, me conta sobre o que achou da implementação da CRUD API REST. Caso tiver dúvida fique à vontade em perguntar também 😄

--

--