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

Alex Felipe
CollabCode
Published in
11 min readOct 2, 2017

No post onde aprendemos a criar um WS base com o Spring Boot, conseguimos implementar um projeto bem básico que devolve um objeto em JSON em uma requisição GET, ou seja, demos apenas o nosso passo inicial…

Entretanto, como sabemos, este tipo de implementação tem como objetivo, chegar em um resultado no qual conhecemos como API REST.

Em outras palavras, a ideia é que criemos um WS que seja capaz de realizar tarefas comuns de API REST, como é o caso dos famosos CRUD.

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 😉

Considerando o exemplo do projeto Ceep, para que o nosso WS seja uma API REST ele precisa tanto permitir adicionar, buscar, alterar e remover um recurso, que no nosso caso, é uma nota.

Pensando em implementar esse ambiente, veremos como podemos fazer isso no Spring Boot.

Recebendo objetos via requisição POST do HTTP

Para darmos início na construção deste CRUD, vamos criar uma função no nosso Controller capaz de receber um objeto que vamos enviar via requisição HTTP com o método POST, ou seja, a função add:

Adicionando função add no NoteController

Em seguida, iremos indicar que essa função será executada a partir de um método do tipo POST com a annotation @PostMapping:

Adicionando annotation @PostMapping na função add

Mas repara que agora, a nossa intenção é de fato receber alguma coisa, ou melhor, queremos receber uma nota, que por sinal, já representamos por meio da nossa classe Note

Então como podemos indicar para o Spring que a nossa função vai receber esse objeto? Simples! Basta apenas enviarmos via parâmetro:

Enviando um objeto do tipo Note como parâmetro na função add

Além disso, o Spring precisa saber de onde esse objeto vem. No nosso caso, iremos enviar via corpo da requisição HTTP. Para indicarmos essa origem, basta apenas adicionarmos a annotation @RequestBody:

Indicando a origem do objeto como corpo da requisição

Agora que recebemos um objeto via corpo da requisição HTTP, como podemos verificar se realmente estamos o recebendo?

Uma das maneiras, é devolvendo o objeto da mesma maneira como devolvemos a lista na função list:

Retornando o objeto note na função

Será que realmente funciona? Bora testar!

Para o teste, basta apenas montarmos uma requisição HTTP com o método POST enviando o header Content-Type com o valor application/json, pois enviaremos um documento JSON, e então, precisamos apenas mandar o nosso objeto no corpo da requisição, como por exemplo:

{
"title": "Nota de teste",
"description": "Teste para o Spring Boot"
}

Para que o Spring faça o processo de bind, isto é, vincule os campos do JSON com os do objeto Note, é necessário que os campos tenham os mesmos nomes

Caso não tenha ideia de como você pode realizar requisições HTTP, sugiro utilizar o Postman, com ele a requisição fica da seguinte maneira:

Realizando requisição POST com a URL http://localhost:8080/notes com o Postman

Então, clicando em Send, temos o seguinte resultado:

Retorno 400 na requisição POST

Ué, tomamos um 400 Bad Request… Por que isso aconteceu?

Entendendo a forma como o Spring serializa e deserializa objetos

Se observamos o modelo que representa a nossa nota:

Classe modelo Note

Chegamos a conclusão de que todos que forem utilizá-la, precisam obrigatoriamente enviar os parâmetros title e description que também são properties… Mas agora você deve estar se perguntando:

“O que isso tem a ver com o problema que aconteceu?”

Por padrão, o Spring Boot utiliza a lib Jackson para realizar o processo de serialização e deserialização de objetos.

Essa ferramenta por sua vez, precisa de um construtor default para construir os objetos durante o processo, isto é, o famoso construtor que não recebe nenhum parâmetro!

Em outras palavras, já que o Jackson não está tendo acesso a esse construtor default, não é possível construir o objeto do tipo Note como esperado…

“Então como podemos criar um construtor default e manter o construor personalizado?”

Sobrecarga com o construtor secundário

No Kotlin, além do construtor primário, as classes também possuem construtores secundários, que de modo geral, nos permite realizar uma sobrecarga de construtor.

Em outras palavras, temos a capacidade de criar um construtor sem parâmetros com um construtor secundário. Para isso, basta apenas colocar a keyword construtor dentro do corpo da classe:

Adicionando construtor secundário sem argumentos

Porém, quando temos um construtor primário e declaramos um construtor secundário, somos obrigados a chamar o construtor primário! Fazemos isso por meio da keyword this:

Chamando o construtor primário a partir do construtor secundário

Calma aí! O código não compila! O que aconteceu?

Por mais que estejamos utilizando um construtor secundário sem argumentos, o nosso construtor primário precisa receber os parâmetros também… E agora?

Utilizando valores padrões nos construtores

Quando entramos neste cenário, basicamente precisamos adicionar valores padrões para o construtor primário, como por exemplo, nesse caso, Strings vazias para indicar um objeto que não teve suas properties inicializadas:

Adicionando valores padrões para o construtor primário a partir do construtor secundário

Se testarmos novamente o nosso servidor:

A requisição funciona! E repara que os valores que enviamos foram computados corretamente! Inclusive, podemos deixar o nosso código mais enxuto ainda:

“Sério mesmo?”

Quando entramos nesse cenário, podemos indicar os valores padrões das properties diretamente pelo construtor primário:

Adicionando valores padrões diretamente no construtor primário

Dessa forma, o próprio Kotlin já gera um construtor padrão e considera que não somos mais obrigados a enviar os parâmetros para o construtor primário, ou seja, não precisamos mais do construtor secundário que foi criado apenas para disponibilizar um construtor default sem parâmetros:

Deixando valores padrões apenas no construtor primário e removendo o construtor secundário

Realizando o teste novamente:

Uhul! Funciona como o esperado! E mais, não precisamos aumentar o nosso código para obter o mesmo comportamento… Isso significa que já podemos começar com o processo de persistência de dados 😃

Essa característica de receber valores padrões no construtor primário para disponibilizar um construtor default, é uma solução comum e que a própria documentação do Kotlin indica para casos em libs que usam Reflection, como o Jackson ou Hibernate, precisam de um construtor sem parâmetros.

Persistência no Spring Boot com o módulo Spring Data JPA

Quando configuramos a dependência JPA pela ferramenta Spring Initializr, por debaixo dos panos adicionamos o projeto Spring Data JPA que trata-se de um módulo do Spring Framework focado em facilitar o processo de persistência de dados utilizando o conceito de ORM a partir da especificação da JPA.

Para o Spring Data funcionar, o nosso primeiro passo é configurar o banco de dados que ele vai usar no processo de persistência, pois ele nada mais é do que uma interface entre a aplicação e a base de dados. Porém como fazemos essa configuração?

Configurando as informações de persistência de dados no Spring Boot

No Spring Boot, temos o arquivo application.properties que trata-se de um arquivo que permite setar configurações que serão consideradas dentro do projeto, como por exemplo, a configuração do nosso banco de dados.

Esse arquivo fica localizado no diretório ceepws/src/main/resources/application.properties. Dentro dele, vamos adicionar o seguinte script:

Configuração para permitir que o Spring Data opere com o HSQLDB

Nesse momento você deve estar pensando:

“Que diabos é essa configuração?”

Não se preocupe, vamos entender agora o que cada uma das configurações significam:

  • spring.datasource.url: Trata-se do endereço do banco de dados que será utilizado, ou seja, jdbc:hsqldb:file:database/main/db

É válido mencionar que essa configuração também indica que o banco de dados será armazenado em um arquivo (file) dentro do diretório database/main/db.

  • spring.datasource.username: como próprio nome diz é o usuário do banco de dados, como podemos ver, está sendo o sa

o valor sa é o usuário padrão do hsqldb.

  • spring.datasource.driver-class-name: Driver que permite a comunicação entre a aplicação e o banco de dados HSQLDB por meio do JDBC, por isso adicionamos org.hsqldb.jdbc.JDBCDriver
  • spring.jpa.properties.hibernate.dialect: SQL que o Hibernate vai gerar durante o processo de transação com o banco de dados. Nesse caso o valor org.hibernate.dialect.HSQLDialect indica que o SQL gerado dará suporte ao HSQLDB
  • spring.jpa.hibernate.ddl-auto: Nessa configuração indicamos ao Hibernate que ele vai criar as tabelas automaticamente de acordo com as entidades do ORM, por isso do valor ser update

Configuração por meio de arquivos YAML

Além do padrão de configuração por meio application.properties, temos a capacidade de configurarmos o Spring Boot com arquivos YAML. Para isso basta apenas renomearmos o arquivo application.properties para application.yml.

Considerando as mesmas configurações do nosso application.properties, com um arquivo YAML, teríamos o seguinte resultado:

Configuração equivalente ao que fizemos no application.properties com YAML

Particularmente, prefiro a configuração via YAML por questões de legibilidade e fácil configuração. Entretanto, fique à vontade em escolher o formato que preferir

Pronto, já realizamos a configuração para que a nossa aplicação se comunique com o banco de dados, mas agora vem a questão:

“E no código? Como fica?”

Criando entidade no Hibernate

Com a configuração feita, o nosso próximo passo é configurar uma entidade utilizando o conceito de ORM a partir da especificação da JPA, ou seja, vamos transformar a classe Note em uma entidade do Hibernate:

Transformando a classe Note em uma entidade do Hibernate

Observe que para isso utilizamos algumas anotações da especificação da JPA, como também adicionamos uma property Id, vamos entender cada uma das mudanças:

  • @Entity: indica que a classe Note vai será considerada uma entidade controlada pelo Hibernate
  • @Id: torna a property id como primary key da entidade
  • @GeneratedValue: configura a primary key para que seja computada automaticamente de forma incremental.

Caso tiver interesse em saber mais sobre o Hibernate, deixo as referências:

Pronto! Configuramos a nossa classe como uma entidade do Hibernate, o nosso próximo passo é configurar o Spring Data para que utilize a nossa entidade, então, dessa forma, seremos capazes que persistir nossas notas! \o/

Configurando o Spring Data

Dentro do Spring Boot, para configurarmos o Spring Data, precisamos apenas criar entidades conhecidas como Repositories que, basicamente, são interfaces que gerenciam os nossos domínios (nossos modelos) para realizar a comunicação com a base de dados.

Em outras palavras, vamos criar um repositório que vai ficar responsável em realizar todas as transações com a nossa classe Note. O primeiro passo para criarmos um repositório, é criando a interface NoteRepository:

Criando a interface para representar o repositório de notas

Em seguida, temos que transformar a interface NoteRepository em um repositório do Spring Data. A maneira pela qual fazemos essa transformação é estendendo de uma interface Repository do Spring Data.

Como o nosso intuito é criar um repositório capaz de fazer um CRUD, a interface comum para esse tipo de ação é justamente a CrudRepository:

Transaformando o NoteRepository em CrudRepository

Entretanto, repara que ela exige o envio de generics que serve para indicar o tipo do domínio e do id respectivamente, portanto, podemos enviar os argumentos Note e Long:

Adicionando o domínio do CrudRepository com a classe Note e Long

Declaramos o nosso repositório do Spring Data e já podemos utilizá-lo 😄

“Mas só isso?”

Parece até mágica, porém, a ideia do projeto Spring Data é facilitar ao máximo o processo de persistência de dados, comum em classe DAO, ou melhor dizendo, um repositório do Spring Data, é como se fosse um DAO que costumamos ver em outros projetos.

Utilizando o repositório do Spring Data por meio de injeção

Para utilizarmos o nosso NoteRepository, basta apenas pedirmos para o Spring injetar uma instância dele pra nós, pois, dessa forma, não temos que gerenciar nenhum estado do objeto, como também, não precisamos saber como é realizada a instância do mesmo.

Em outras palavras, estaremos delegando toda essa responsabilidade para o Spring Framework, que por sinal, trata-se de um comportamento comum do módulo core do Spring que tecnicamente é conhecido como inversão de controle.

Caso quiser ter uma introdução do assunto deixo a referência. Para um conhecimento mais profundo recomendo fortemente a documentação do Spring Framework

No Spring, a forma pela qual indicamos que um objeto vai ser injetado é por meio da annotation @Autowired, portanto, basta apenas criarmos uma property do tipo NoteRepository dentro do nosso controller e anotá-la:

Adicionando a property noteRepository com a annotation @Autowired

Mas repara que entramos em um caso específico do Kotlin, que é justamente a ideia de que todas as variáveis ou properties precisam ser inicializadas… Complicou, né?

Criando properties com inicialização atrasada no Kotlin

Para lidarmos com esse tipo de situação na qual precisamos que nossas properties sejam inicializadas de forma atrasada, isto é, em algum outro momento, sem depender da instância do objeto por exemplo, o Kotlin nos fornece a incialização lateinit.

lateinit val noteRepository: NoteRepository

Porém para criarmos uma property de inicialização atrasada, temos diversas restrições, e uma delas é que a property necessariamente precisa ser var:

Adicionando inicialização atrasada com lateinit

Se tentarmos acessar essa variável no código sendo que ela ainda não tenha sido inicializada, recebemos uma exception!

Existem outros detalhes relacionados a esse tipo de inicialização que não serão discutidos nesse artigo, mas deixo a referência.

Com um repositório em mãos, podemos usar suas funções para realizar nosso CRUD, vamos começar com a função list(), nela usaremos a função findAll() do noteRepository para devolver uma lista com todas as notas que foram persistidas:

Devolvendo todas as notas salvas com a função findAll

Como o findAll() devolve um Iterable, precisamos converter para uma lista, pois o retorno é uma lista de notas, por isso chamamos também a função toList().

Na funçãoadd(), vamos pedir para o repositório salvar a nota que recebemos, para isso chamamos a função save():

Salvando nota no repositório do Spring

A função save() devolve o próprio objeto que foi salvo na base de dados, a diferença é que ele é devolvido com o mesmo id que foi salvo no banco de dados.

Testando o servidor após a configuração do Spring Boot

Com o nosso controller configurado para salvar e listar todas as nossas notas, vamos reiniciar o Spring Boot para testarmos.

Observe que após reiniciar o server, surgiu o diretório database:

É justamente o diretório que configuramos no application.properties, ou seja, todos os nossos dados da aplicação estarão nele. Se testarmos uma requisição GET:

Retorno de uma lista vazia para um requisição GET

Veja que recebemos uma lista vazia, pois ainda não salvamos nenhuma nota! Sendo assim, vamos fazer aquela mesma requisição POST que fizemos anteriormente:

Salvando nota com requisição POST

Opa! Repara que agora foi devolvida a nossa nota que enviamos para salvar, porém, ela retornou com o id com valor 1. Vamos tentar pegar novamente todas as notas:

Uhul! Conseguimos criar uma API que nos permite tanto salvar, como também, listar notas.

Código fonte

Caso ficar com alguma dúvida na implementação, fique à vontade em consultar o código fonte que deixei no GitHub.

Para saber mais

Além da interface CrudRepository, o Spring Data também disponibiliza outras interfaces de repositórios com mais comportamentos:

Além disso, veja que nesse artigo utilizamos funções prontas, como o findAll(), e a save(). Entretanto, é possível criar funções baseando-se em properties das entitidades, como por exemplo, uma busca por título:

Criando uma função para buscar uma nota a partir de um repository

Dessa forma, basta apenas enviarmos o título via parâmetro que será devolvida uma nota com o mesmo título.

Para mais detalhes sobre o Spring Data recomendo fortemente que consulte a documentação.

Conclusão

Neste artigo, aprendemos a receber objetos via requisição POST por meio do corpo da requisição HTTP.

Também vimos como podemos configurar o Spring Data para facilitar a nossa vida durante o processo de persistência com a nossa base de dados. Além disso, vimos que o Kotlin também nos disponibiliza uma técnica de inicialização atrasada com o lateinit.

Por fim, conseguimos fazer com que a nossa API REST permitisse tanto a devolução de todas as notas salvas, como também, salvar as mesmas.

Ficamos apenas por aqui? Ainda tem mais coisas pela frente! Ou seja, no segundo artigo, veremos com é possível alterar e remover notas da nossa API.

E aí, o que achou do Kotlin com Spring Boot? Aproveite e deixe o seu comentário 😄

--

--