Criando uma API com Spring Boot e Kotlin — parte 2
Tags: kotlin, spring, api
Published: 2022-11-06 03:18:42

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:

```kotlin
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:

```kotlin
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:

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:

```yaml
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á:

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:

```kotlin
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.

## 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`:

```kotlin
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!

## 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`:

```json
{
  "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:

```json
{
    "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):

 

```json
{
  "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:

```json
{
    "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:

```json
[
    {
        "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!

## 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:

{% embed https://github.com/ronaldocoding/pchunter-api %}

Post anterior:

{% embed https://dev.to/ronaldocoding/criando-uma-api-com-spring-boot-e-kotlin-parte-1-3pkb %}

Próximo post:

{% embed https://dev.to/ronaldocoding/criando-uma-api-com-spring-boot-e-kotlin-parte-3-54p1 %}

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