Criando uma API com Spring Boot e Kotlin — parte 2

ronaldocoding

Ronaldo Costa de Freitas

Posted on November 6, 2022

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!

💖 💪 🙅 🚩
ronaldocoding
Ronaldo Costa de Freitas

Posted on November 6, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related