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

2

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!

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay