DEV Community

Cover image for Criando uma API com Spring Boot e Kotlin — parte 2
Ronaldo Costa de Freitas
Ronaldo Costa de Freitas

Posted on • Edited on

Criando uma API com Spring Boot e Kotlin — parte 2

Seguindo nossa série de posts sobre como construir uma API simples para servir de back-end para um app de construção de setups de PC que criamos há algum tempo, hoje vamos escrever nossas classes models, configurar nosso banco de dados e implementar as requisições GET e POST do nosso serviço.

Criando classes models

Para representar um Setup com todas as suas peças vamos precisar de duas entidades: Part, para representar uma peça, e Setup, para representar um Setup.

Código da classe Part:

package br.com.pchunter.model import com.fasterxml.jackson.annotation.JsonProperty import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.Id @Entity data class Part( @Id @GeneratedValue @JsonProperty(value = "id", access = JsonProperty.Access.READ_ONLY) val id: Long, val title: String, val description: String, val url: String, val value: Float ) 
Enter fullscreen mode Exit fullscreen mode

É uma classe bem simples composta por propriedades para representar seu título, descrição, url e valor. Também temos a propriedade id que é autogerada. A notação de mesmo nome indica que propriedade é um identificador no banco de dados, GeneratedValue significa que seu valor será gerado ao ser criada uma instância dessa entidade, JsonProperty, em resumo, protege a propriedade identificadora de ser alterada. Além disso, a notação Entity indica que essa classe é a representação de uma entidade do banco de dados.

Código da classe Setup:

package br.com.pchunter.model import com.fasterxml.jackson.annotation.JsonProperty import javax.persistence.* @Entity data class Setup( @Id @GeneratedValue @JsonProperty(value = "id", access = JsonProperty.Access.READ_ONLY) val id: Long, @OneToOne(cascade = [CascadeType.PERSIST]) val cpu: Part, @OneToOne(cascade = [CascadeType.PERSIST]) val motherboard: Part, @OneToMany(cascade = [CascadeType.PERSIST]) val gpus: List<Part>, @OneToMany(cascade = [CascadeType.PERSIST]) val hds: List<Part>, @OneToMany(cascade = [CascadeType.PERSIST]) val ssds: List<Part>, @OneToMany(cascade = [CascadeType.PERSIST]) val rams: List<Part>, @OneToMany(cascade = [CascadeType.PERSIST]) val fans: List<Part>, @OneToOne(cascade = [CascadeType.PERSIST]) val powerSupply: Part, @OneToOne(cascade = [CascadeType.PERSIST]) val cabinet: Part, @OneToMany(cascade = [CascadeType.PERSIST]) val monitors: List<Part>, @OneToOne(cascade = [CascadeType.PERSIST]) val keyboard: Part, @OneToOne(cascade = [CascadeType.PERSIST]) val mouse: Part ) { val totalValue: Float get() { var acm = 0.0f acm += cpu.value + motherboard.value + powerSupply.value + cabinet.value + keyboard.value + mouse.value gpus.forEach { gpu -> acm += gpu.value } hds.forEach { hd -> acm += hd.value } ssds.forEach { ssd -> acm += ssd.value } rams.forEach { ram -> acm += ram.value } fans.forEach { fan -> acm += fan.value } monitors.forEach { monitor -> acm += monitor.value } return acm } } 
Enter fullscreen mode Exit fullscreen mode

A classe Setup é composta por várias classes Part, cada uma representando uma peça. Temos também a propriedade totalValue que retorna o valor total do setup, sendo composto pela soma dos valores de todas as peças.

Em relação às anotações, temos algumas novas aqui:

  1. OneToOne: indica que a relação entre a entidade Setup e essa outra entidade específica é de 1:1, ou seja, um setup tem apenas uma entidade dessa.
  2. OneToMany: indica que a relação entre a entidade Setup e essa outra entidade é de 1:N, ou seja, um setup pode ter várias dessa entidade.
  3. cascade = [CascadeType.PERSIST]: significa que a operação de persistência será persistida de pai para filho, ou seja, sempre que um setup for salvo, todas as suas peças também serão.

Pronto: já temos nossas entidades da camada model.

Configurando o banco de dados

A configuração do nosso banco de dados será bem simples, primeiro nos vamos renomear o arquivo application.properties para application.yaml e escrever o seguinte:

spring: datasource: url: jdbc:hsqldb:file:database/main/db username: ronaldo driver-class-name: org.hsqldb.jdbc.JDBCDriver jpa: properties.hibernate.dialect: org.hibernate.dialect.HSQLDialect hibernate.ddl-auto: update 
Enter fullscreen mode Exit fullscreen mode

Aqui temos uma sopa de letrinhas, vamos lá:

  1. spring:datasource:url: é o endereço do banco de dados que será utilizado
  2. spring:datasource:username: é o nome do usuário do banco de dados
  3. spring:datasource:driver-class-name: é o driver que permite a comunicação entre nossa aplicação e o HSQLDB
  4. jpa:properties.hibernate.dialect: indica que o SQL vai reconhecer o HSQLDB
  5. jpa:properties.hibernate.dialect: indica ao Hibernate que ele deve gerar as tabelas de acordo com as entidades do ORM

Para finalizar a configuração do banco, vamos viabilizar o nosso CRUD, para isso vamos criar uma interface que estenda de CrudRepository do Spring Data:

package br.com.pchunter.repository import br.com.pchunter.model.Setup import org.springframework.data.repository.CrudRepository interface SetupRepository : CrudRepository<Setup, Long> {} 
Enter fullscreen mode Exit fullscreen mode

Parece até mentira, mas é só isso mesmo: delegamos toda a tarefa de persistir os dados, criar um repositório, injetar a dependência para o Spring e já temos acesso a funções como findAll() e save() que logo usaremos.

Requisições GET e POST

Agora basta implementar as funções GET e POST para finalizar nosso trabalho de hoje, para isso vamos criar uma classe controller. Uma classe Controller em Spring é uma classe responsável por lidar com requisições web.

Segue nossa SetupController:

package br.com.pchunter.controller import br.com.pchunter.model.Setup import br.com.pchunter.repository.SetupRepository import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("setup") class SetupController { @Autowired lateinit var repository: SetupRepository @GetMapping fun getAllSetups(): List<Setup> = repository.findAll().toList() @PostMapping fun addSetup(@RequestBody setup: Setup) = repository.save(setup) } 
Enter fullscreen mode Exit fullscreen mode

Logo no início usamos duas novas notações: @RestController, significa que nossa classe é um controller que atende à uma API REST, @RequestMapping, indica qual o caminho da url que esse controller está mapeando.

Depois nós delegamos a injeção da dependência do nosso repositório para o Spring por meio do @Autowired. Repare que a propriedade é uma lateinit var, ou seja, ela será inicializada no futuro.

Por fim, finalmente temos a implementação dos nossos métodos GET e POST.

Nosso método GET é bem simples, ele retorna todos os Setup salvos no nosso banco de dados por meio do método findAll(), depois convertemos o resultado para o tipo List<Setup>. Como é um método GET, usamos a notação @GetMapping, indicando que o método mapeia esse tipo de requisição.

Quanto ao nosso método POST, ele recebe um Setup por meio do corpo (*body*) da requisição, por isso usamos a notação @RequestBody para mapear a propriedade, é salvo e retornamos esse Setup salvo no banco de dados usando a função save(). Do mesmo modo, usamos a notação @PostMapping, para indicar que o método mapeia uma requisição POST.

Agora sim, já podemos testar nossa API!

Testando a API

Para começar, vamos testar a função que mapeia a requisição POST. Mandamos esse seguinte JSON para a nossa API usando o Postman:

{ "cpu": { "title": "Cpu", "description": "Cpu", "url": "cpu.com", "value": 879.0 }, "motherboard": { "title": "Motherboard", "description": "Motherboard", "url": "motherboard.com", "value": 765.0 }, "gpus": [ { "title": "Gpu", "description": "Gpu", "url": "gpu.com", "value": 1600.9 } ], "hds": [ { "title": "Hd", "description": "Hd", "url": "hd.com", "value": 300.9 } ], "ssds": [ { "title": "Ssd", "description": "Ssd", "url": "ssd.com", "value": 350.99 } ], "rams": [ { "title": "Ram", "description": "Ram", "url": "ram.com", "value": 250.9 } ], "fans": [ { "title": "Fan", "description": "Fan", "url": "fan.com", "value": 125.99 } ], "powerSupply": { "title": "PSU", "description": "PSU", "url": "psu.com", "value": 450.9 }, "cabinet": { "title": "Cabinet", "description": "Cabinett", "url": "cabinet.com", "value": 300.9 }, "monitors": [ { "title": "Monitor", "description": "Monitor", "url": "monitor.com", "value": 1800.9 } ], "keyboard": { "title": "Keyboard", "description": "Keyboard", "url": "keyboard.com", "value": 800.9 }, "mouse": { "title": "Mouse", "description": "Mouse", "url": "mouse.com", "value": 350.9 } } 
Enter fullscreen mode Exit fullscreen mode

E temos esse resultado:

{ "id": 44, "cpu": { "id": 46, "title": "Cpu", "description": "Cpu", "url": "cpu.com", "value": 879.0 }, "motherboard": { "id": 48, "title": "Motherboard", "description": "Motherboard", "url": "motherboard.com", "value": 765.0 }, "gpus": [ { "id": 52, "title": "Gpu", "description": "Gpu", "url": "gpu.com", "value": 1600.9 } ], "hds": [ { "id": 53, "title": "Hd", "description": "Hd", "url": "hd.com", "value": 300.9 } ], "ssds": [ { "id": 56, "title": "Ssd", "description": "Ssd", "url": "ssd.com", "value": 350.99 } ], "rams": [ { "id": 55, "title": "Ram", "description": "Ram", "url": "ram.com", "value": 250.9 } ], "fans": [ { "id": 51, "title": "Fan", "description": "Fan", "url": "fan.com", "value": 125.99 } ], "powerSupply": { "id": 50, "title": "PSU", "description": "PSU", "url": "psu.com", "value": 450.9 }, "cabinet": { "id": 45, "title": "Cabinet", "description": "Cabinett", "url": "cabinet.com", "value": 300.9 }, "monitors": [ { "id": 54, "title": "Monitor", "description": "Monitor", "url": "monitor.com", "value": 1800.9 } ], "keyboard": { "id": 47, "title": "Keyboard", "description": "Keyboard", "url": "keyboard.com", "value": 800.9 }, "mouse": { "id": 49, "title": "Mouse", "description": "Mouse", "url": "mouse.com", "value": 350.9 }, "totalValue": 7978.1797 } 
Enter fullscreen mode Exit fullscreen mode

Repare que todos os IDs foram criados, para cada entidade, e também temos o valor total do Setup na chave-valor totalValue.

Vamos usar novamente essa requisição mandando esse json (praticamente igual ao anterior):

{ "cpu": { "title": "Tpu", "description": "Cpu", "url": "cpu.com", "value": 879.0 }, "motherboard": { "title": "Motherboard", "description": "Motherboard", "url": "motherboard.com", "value": 765.0 }, "gpus": [ { "title": "Gpu", "description": "Gpu", "url": "gpu.com", "value": 1600.9 } ], "hds": [ { "title": "Hd", "description": "Hd", "url": "hd.com", "value": 300.9 } ], "ssds": [ { "title": "Ssd", "description": "Ssd", "url": "ssd.com", "value": 350.99 } ], "rams": [ { "title": "Ram", "description": "Ram", "url": "ram.com", "value": 250.9 } ], "fans": [ { "title": "Fan", "description": "Fan", "url": "fan.com", "value": 125.99 } ], "powerSupply": { "title": "PSU", "description": "PSU", "url": "psu.com", "value": 450.9 }, "cabinet": { "title": "Cabinet", "description": "Cabinett", "url": "cabinet.com", "value": 300.9 }, "monitors": [ { "title": "Monitor", "description": "Monitor", "url": "monitor.com", "value": 1800.9 } ], "keyboard": { "title": "Keyboard", "description": "Keyboard", "url": "keyboard.com", "value": 800.9 }, "mouse": { "title": "Mouse", "description": "Mouse", "url": "mouse.com", "value": 350.9 } } 
Enter fullscreen mode Exit fullscreen mode

E temos o seguinte resultado:

{ "id": 70, "cpu": { "id": 72, "title": "Tpu", "description": "Cpu", "url": "cpu.com", "value": 879.0 }, "motherboard": { "id": 74, "title": "Motherboard", "description": "Motherboard", "url": "motherboard.com", "value": 765.0 }, "gpus": [ { "id": 78, "title": "Gpu", "description": "Gpu", "url": "gpu.com", "value": 1600.9 } ], "hds": [ { "id": 79, "title": "Hd", "description": "Hd", "url": "hd.com", "value": 300.9 } ], "ssds": [ { "id": 82, "title": "Ssd", "description": "Ssd", "url": "ssd.com", "value": 350.99 } ], "rams": [ { "id": 81, "title": "Ram", "description": "Ram", "url": "ram.com", "value": 250.9 } ], "fans": [ { "id": 77, "title": "Fan", "description": "Fan", "url": "fan.com", "value": 125.99 } ], "powerSupply": { "id": 76, "title": "PSU", "description": "PSU", "url": "psu.com", "value": 450.9 }, "cabinet": { "id": 71, "title": "Cabinet", "description": "Cabinett", "url": "cabinet.com", "value": 300.9 }, "monitors": [ { "id": 80, "title": "Monitor", "description": "Monitor", "url": "monitor.com", "value": 1800.9 } ], "keyboard": { "id": 73, "title": "Keyboard", "description": "Keyboard", "url": "keyboard.com", "value": 800.9 }, "mouse": { "id": 75, "title": "Mouse", "description": "Mouse", "url": "mouse.com", "value": 350.9 }, "totalValue": 7978.1797 } 
Enter fullscreen mode Exit fullscreen mode

E finalmente vamos usar a requisição GET:

[ { "id": 44, "cpu": { "id": 46, "title": "Cpu", "description": "Cpu", "url": "cpu.com", "value": 879.0 }, "motherboard": { "id": 48, "title": "Motherboard", "description": "Motherboard", "url": "motherboard.com", "value": 765.0 }, "gpus": [ { "id": 52, "title": "Gpu", "description": "Gpu", "url": "gpu.com", "value": 1600.9 } ], "hds": [ { "id": 53, "title": "Hd", "description": "Hd", "url": "hd.com", "value": 300.9 } ], "ssds": [ { "id": 56, "title": "Ssd", "description": "Ssd", "url": "ssd.com", "value": 350.99 } ], "rams": [ { "id": 55, "title": "Ram", "description": "Ram", "url": "ram.com", "value": 250.9 } ], "fans": [ { "id": 51, "title": "Fan", "description": "Fan", "url": "fan.com", "value": 125.99 } ], "powerSupply": { "id": 50, "title": "PSU", "description": "PSU", "url": "psu.com", "value": 450.9 }, "cabinet": { "id": 45, "title": "Cabinet", "description": "Cabinett", "url": "cabinet.com", "value": 300.9 }, "monitors": [ { "id": 54, "title": "Monitor", "description": "Monitor", "url": "monitor.com", "value": 1800.9 } ], "keyboard": { "id": 47, "title": "Keyboard", "description": "Keyboard", "url": "keyboard.com", "value": 800.9 }, "mouse": { "id": 49, "title": "Mouse", "description": "Mouse", "url": "mouse.com", "value": 350.9 }, "totalValue": 7978.1797 }, { "id": 57, "cpu": { "id": 59, "title": "Tpu", "description": "Cpu", "url": "cpu.com", "value": 879.0 }, "motherboard": { "id": 61, "title": "Motherboard", "description": "Motherboard", "url": "motherboard.com", "value": 765.0 }, "gpus": [ { "id": 65, "title": "Gpu", "description": "Gpu", "url": "gpu.com", "value": 1600.9 } ], "hds": [ { "id": 66, "title": "Hd", "description": "Hd", "url": "hd.com", "value": 300.9 } ], "ssds": [ { "id": 69, "title": "Ssd", "description": "Ssd", "url": "ssd.com", "value": 350.99 } ], "rams": [ { "id": 68, "title": "Ram", "description": "Ram", "url": "ram.com", "value": 250.9 } ], "fans": [ { "id": 64, "title": "Fan", "description": "Fan", "url": "fan.com", "value": 125.99 } ], "powerSupply": { "id": 63, "title": "PSU", "description": "PSU", "url": "psu.com", "value": 450.9 }, "cabinet": { "id": 58, "title": "Cabinet", "description": "Cabinett", "url": "cabinet.com", "value": 300.9 }, "monitors": [ { "id": 67, "title": "Monitor", "description": "Monitor", "url": "monitor.com", "value": 1800.9 } ], "keyboard": { "id": 60, "title": "Keyboard", "description": "Keyboard", "url": "keyboard.com", "value": 800.9 }, "mouse": { "id": 62, "title": "Mouse", "description": "Mouse", "url": "mouse.com", "value": 350.9 }, "totalValue": 7978.1797 }, { "id": 70, "cpu": { "id": 72, "title": "Tpu", "description": "Cpu", "url": "cpu.com", "value": 879.0 }, "motherboard": { "id": 74, "title": "Motherboard", "description": "Motherboard", "url": "motherboard.com", "value": 765.0 }, "gpus": [ { "id": 78, "title": "Gpu", "description": "Gpu", "url": "gpu.com", "value": 1600.9 } ], "hds": [ { "id": 79, "title": "Hd", "description": "Hd", "url": "hd.com", "value": 300.9 } ], "ssds": [ { "id": 82, "title": "Ssd", "description": "Ssd", "url": "ssd.com", "value": 350.99 } ], "rams": [ { "id": 81, "title": "Ram", "description": "Ram", "url": "ram.com", "value": 250.9 } ], "fans": [ { "id": 77, "title": "Fan", "description": "Fan", "url": "fan.com", "value": 125.99 } ], "powerSupply": { "id": 76, "title": "PSU", "description": "PSU", "url": "psu.com", "value": 450.9 }, "cabinet": { "id": 71, "title": "Cabinet", "description": "Cabinett", "url": "cabinet.com", "value": 300.9 }, "monitors": [ { "id": 80, "title": "Monitor", "description": "Monitor", "url": "monitor.com", "value": 1800.9 } ], "keyboard": { "id": 73, "title": "Keyboard", "description": "Keyboard", "url": "keyboard.com", "value": 800.9 }, "mouse": { "id": 75, "title": "Mouse", "description": "Mouse", "url": "mouse.com", "value": 350.9 }, "totalValue": 7978.1797 } ] 
Enter fullscreen mode Exit fullscreen mode

E assim, os dois setups criados foram retornados com sucesso!

Próximos posts

Nossa API ainda tem muito o que melhorar: ainda precisamos implementar os métodos PUT e DELETE, melhorar sua segurança e adicionar testes. Vamos fazer isso nós próximos dois posts.

Link do repo no github:

pchunter-api

PCHunter API






Post anterior:

Próximo post:

Obrigado pela atenção e até a próxima!

Top comments (0)