pchunter-api
PCHunter API
Posted on November 6, 2022
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.
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
)
É 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
}
}
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:
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.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.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
.
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
Aqui temos uma sopa de letrinhas, vamos lá:
spring:datasource:url
: é o endereço do banco de dados que será utilizadospring:datasource:username
: é o nome do usuário do banco de dadosspring:datasource:driver-class-name
: é o driver que permite a comunicação entre nossa aplicação e o HSQLDBjpa:properties.hibernate.dialect
: indica que o SQL vai reconhecer o HSQLDBjpa:properties.hibernate.dialect
: indica ao Hibernate que ele deve gerar as tabelas de acordo com as entidades do ORMPara 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> {}
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.
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)
}
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!
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
}
}
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
}
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
}
}
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
}
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
}
]
E assim, os dois setups criados foram retornados com sucesso!
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:
Post anterior:
Próximo post:
Obrigado pela atenção e até a próxima!
Posted on November 6, 2022
Sign up to receive the latest update from our blog.