Kotlin com Spring Boot – Criando uma Api Rest Passo a Passo

Compartilhar:

Fala pessoal! neste artigo, vamos criar uma API RESTful utilizando Kotlin e o framework Spring Boot. Vamos abordar desde a configuração inicial do projeto até a implementação de endpoints para realizar operações CRUD (Create, Read, Update, Delete) em uma entidade simples.

Kotlin e Spring Boot são duas tecnologias que, quando combinadas, oferecem um ambiente poderoso e eficiente para o desenvolvimento de APIs REST.

Kotlin, uma linguagem de programação moderna e concisa, tem ganhado popularidade devido à sua interoperabilidade com o Java e sua sintaxe limpa. Por outro lado, o Spring Boot, um framework amplamente utilizado para desenvolvimento rápido de aplicativos Java, oferece recursos robustos e uma configuração mínima.

kotlin com spring boot

Configuração do Projeto

Para criar um novo projeto Spring Boot com Kotlin, podemos utilizar o Spring Initializr ou criar um projeto manualmente.

Vamos utilizar o Spring Initializr para facilitar o processo. Acesse https://start.spring.io/ e configure o projeto conforme abaixo:

  • Project: Maven Project
  • Language: Kotlin
  • Spring Boot: Escolha a versão mais recente
  • Project Metadata:
  • Dependencies: Adicione Spring Web e Spring Data JPA
  • Artifact: br.com.virandoprogramador
  • Name: demo
  • Description: Kotlin com Spring Boot

Clique em “Generate” para baixar o arquivo zip contendo o projeto inicial.

No arquivo pom.xml vão estar as seguintes dependências:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.module</groupId>
			<artifactId>jackson-module-kotlin</artifactId>
		</dependency>
		<dependency>
			<groupId>org.jetbrains.kotlin</groupId>
			<artifactId>kotlin-reflect</artifactId>
		</dependency>
		<dependency>
			<groupId>org.jetbrains.kotlin</groupId>
			<artifactId>kotlin-stdlib-jdk8</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>

Agora precisamos adicionar algumas configurações de conexão com o banco de dados, em nosso caso vamos utilizar o banco H2 apenas para teste.

Adicione as seguintes configurações para seu banco de dados, no arquivo properties.yml:

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:forum
    username: sa
    password:
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect

Após adicionarmos as dependências necessárias, agora vamos começar a criar as classes para a criação da nossa Api Rest.

Classes do Projeto Spring Boot e Kotlin

Para iniciar, a nossa classe principal do Spring Boot ficou da seguinte maneira:

package br.com.virandoprogramador

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class ForumApplication

fun main(args: Array<String>) {
	runApplication<ForumApplication>(*args)
}

Após rodar nossa aplicação, vamos começar a criar as classes do projeto.

Primeiro, vamos criar as classes de Entidade (Modelo), em nosso exemplo, vamos criar 5 entidades que são: Curso, Respostas, StatusTopico, Tópico e Usuario:

Package: Model:

Entidade Curso:

package br.com.virandoprogramador.model

import javax.persistence.*
@Entity
data class Curso (

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    val nome: String,
    val categoria: String
)

Entidade Respostas:

package br.com.virandoprogramador.model

import java.time.LocalDateTime
import javax.persistence.*
@Entity
data class Respostas (

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long?,

    val mensagem: String,

    val dataCriacao: LocalDateTime = LocalDateTime.now(),

    @ManyToOne
    val autor: Usuario,

    @ManyToOne
    val topico: Topico,

    val solucao: Boolean
)

Entidade StatusTopico:

package br.com.virandoprogramador.model

enum class StatusTopico {
    NAO_RESPONDIDO,
    NAO_SOLUCIONADO,
    SOLUCIONADO,
    FECHADO
}

Entidade Topico:

package br.com.virandoprogramador.model

import java.time.LocalDateTime
import javax.persistence.*
@Entity
data class Topico(

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,

    var titulo: String,

    var mensagem: String,

    val dataCriacao: LocalDateTime = LocalDateTime.now(),

    @OneToMany(mappedBy = "topico")
    val respostas: List<Respostas> = ArrayList(),

    @Enumerated(value = EnumType.STRING)
    val status: StatusTopico = StatusTopico.NAO_RESPONDIDO,

    @ManyToOne
    val curso: Curso,

    @ManyToOne
    val autor: Usuario

)

Entidade Usuario:

package br.com.virandoprogramador.model

import javax.persistence.*

@Entity
data class Usuario (
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    val nome: String,

    val email: String
)

Classes de Persistência de Dados (JPA Repository)

Agora precisamos criar 3 classes para a persistência de dados com Spring Data JPA.

Acima, anexamos um link para um artigo bem completo sobre o assunto, mais para dar continuidade, vamos criar as 3 classes de repository do projeto: CursoRepository, TopicoRepository e UsuarioRepository.

Package: Repository:

CursoRepository:

package br.com.virandoprogramador.repository

import br.com.virandoprogramador.model.Curso
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface CursoRepository: JpaRepository<Curso, Long> {

}

TopicoRepository:

package br.com.virandoprogramador.repository

import br.com.virandoprogramador.model.Topico
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface TopicoRepository: JpaRepository<Topico, Long>{

}

UsuarioRepository:

package br.com.virandoprogramador.repository

import br.com.virandoprogramador.model.Usuario
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface UsuarioRepository: JpaRepository<Usuario, Long> {

}

Classes de DTO

Agora, vamos criar nossas classes DTO, para proteger nossas entidades, utilizamos DTOs ou VOs para ter uma flexibilidade maior na exibição dados em uma Api por exemplo, protegendo alguns dados que não precisam ser exibidos em um resultado.

Para iniciar, crie um pacote chamado dto, para separar as classes das entidades. as classes que vamos criar são as seguintes: AtualizacaoTopicoForm, ErrorView, TopicoDTOForm, TopicoView.

Package: Dto.

AtualizacaoTopicoForm:

package br.com.virandoprogramador.dto

import javax.validation.constraints.NotEmpty
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size
data class AtualizacaoTopicoForm (


    @field:NotNull(message = "ID deve ser Informado!")
    val id: Long,

    @field:NotEmpty(message = "Mensagem deve ser Informado!")
    @field:Size(min = 5, max = 100, message = "Mensagem deve conter entre 5 e 100 Caracteres!")
    val mensagem: String,

    @field:NotEmpty(message = "Titulo deve ser Informado!")
    @field:Size(min = 5, max = 100, message = "Titulo deve conter entre 5 e 100 Caracteres!")
    val titulo: String,

)

ErrorView:

package br.com.virandoprogramador.dto

import java.time.LocalDateTime

data class ErrorView (

    val timestamp: LocalDateTime = LocalDateTime.now(),
    val status: Int,
    val erro: String,
    val message: String?,
    val path: String

)

TopicoDTOForm:

package br.com.virandoprogramador.dto

import javax.validation.constraints.NotEmpty
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size

data class TopicoDTOForm (

    @field:NotEmpty(message = "Titulo deve ser Informado!")
    @field:Size(min = 5, max = 100, message = "Titulo deve conter de 5 a 100 Caracteres!")
    val titulo: String,

    @field:NotEmpty(message = "Mensagem deve ser Informado!")
    val mensagem: String,

    @field:NotNull(message = "ID Curso deve ser Informado!")
    val idCurso: Long,

    @field:NotNull(message = "ID Autor deve ser Informado!")
    val idAutor: Long
)

TopicoView:

package br.com.virandoprogramador.dto

import br.com.virandoprogramador.model.StatusTopico
import java.time.LocalDateTime

data class TopicoView (
        val id: Long?,
        val titulo: String,
        val mensagem: String,
        val status: StatusTopico,
        val dataCriacao: LocalDateTime
    )

Classes complementares – Mapper e Exception

Para dar continuidade, precisamos criar algumas classes complementares em nosso projeto, como Mappers e Exception, para nos ajudar a tratar as exceções e erros.

As classes Mappers vão servir para realizar a conversão de dados para DTO.

Package: Mapper.

Mapper:

package br.com.virandoprogramador.mapper

interface Mapper<T, U> {
    fun map(t: T) :U

}

TopicoFormMapper:

package br.com.virandoprogramador.mapper

import br.com.virandoprogramador.dto.TopicoDTOForm
import br.com.virandoprogramador.model.Topico
import br.com.virandoprogramador.service.CursoService
import br.com.virandoprogramador.service.UsuarioService
import org.springframework.stereotype.Component

@Component
class TopicoFormMapper(private val cursoService: CursoService, private val usuarioService: UsuarioService): Mapper<TopicoDTOForm, Topico> {

    override fun map(t: TopicoDTOForm): Topico {
       return Topico(
            titulo = t.titulo,
            mensagem = t.mensagem,
            curso = cursoService.buscarPorId(t.idCurso),
            autor = usuarioService.buscarPorId(t.idAutor)
        )
    }
}

TopicoViewMapper:

package br.com.virandoprogramador.mapper

import br.com.virandoprogramador.dto.TopicoView
import br.com.virandoprogramador.model.Topico
import org.springframework.stereotype.Component
@Component
class TopicoViewMapper: Mapper<Topico, TopicoView> {

    override fun map(t: Topico): TopicoView {
        return TopicoView(
            id = t.id,
            titulo = t.titulo,
            mensagem = t.mensagem,
            dataCriacao = t.dataCriacao,
            status = t.status
        )
    }
}

Package: Exception.

ExceptionHandler:

package br.com.virandoprogramador.exception

import br.com.virandoprogramador.dto.ErrorView
import org.springframework.http.HttpStatus
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus
import javax.servlet.http.HttpServletRequest

@RestControllerAdvice
class ExceptionHandler {

    @ExceptionHandler(NotFoundException::class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    fun handleNotFound(exception: NotFoundException, request: HttpServletRequest): ErrorView {
        return ErrorView(
            status = HttpStatus.NOT_FOUND.value(),
            erro = HttpStatus.NOT_FOUND.name,
            message = exception.message,
            path = request.servletPath
        )
    }

    @ExceptionHandler(Exception::class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    fun handleServerError(exception: Exception, request: HttpServletRequest): ErrorView {
        return ErrorView(
            status = HttpStatus.INTERNAL_SERVER_ERROR.value(),
            erro = HttpStatus.INTERNAL_SERVER_ERROR.name,
            message = exception.message,
            path = request.servletPath
        )
    }

    @ExceptionHandler(MethodArgumentNotValidException::class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    fun handleBadRequest(exception: MethodArgumentNotValidException, request: HttpServletRequest): ErrorView {
        val errorMessage = HashMap<String, String?>()
        exception.bindingResult.fieldErrors.forEach{e -> errorMessage.put(e.field, e.defaultMessage)}
        return ErrorView(
            status = HttpStatus.BAD_REQUEST.value(),
            erro = HttpStatus.BAD_REQUEST.name,
            message = errorMessage.toString(),
            path = request.servletPath
        )
    }

}

NotFoundException:

package br.com.virandoprogramador.exception

import java.lang.RuntimeException

class NotFoundException(message: String?): RuntimeException(message) {

}

Classes da lógica de negócios (Service)

Agora precisamos criar nossas classes para a lógica de negócio da nossa aplicação, antes mesmo de criarmos nossos controllers.

As classes Services, ficaram responsáveis por toda a lógica do nosso código, se conectando com camadas mais a baixo como Mappers e Repository.

Abaixo, vamos criar 3 classes Services: CursoService, TopicoService, UsuarioService.

Package: Service.

CursoService:

package br.com.virandoprogramador.service

import br.com.virandoprogramador.model.Curso
import br.com.virandoprogramador.repository.CursoRepository
import org.springframework.stereotype.Service

@Service
class CursoService(private val repository: CursoRepository) {

    fun buscarPorId(id: Long): Curso{
        return repository.getOne(id)
    }

}

TopicoService:

package br.com.virandoprogramador.service

import br.com.virandoprogramador.dto.AtualizacaoTopicoForm
import br.com.virandoprogramador.dto.TopicoDTOForm
import br.com.virandoprogramador.dto.TopicoView
import br.com.virandoprogramador.exception.NotFoundException
import br.com.virandoprogramador.mapper.TopicoFormMapper
import br.com.virandoprogramador.mapper.TopicoViewMapper
import br.com.virandoprogramador.repository.TopicoRepository
import org.springframework.stereotype.Service
import java.util.stream.Collectors

@Service
class TopicoService(private val repository: TopicoRepository,
                    private val topicoViewMapper: TopicoViewMapper,
                    private val topicoFormMapper: TopicoFormMapper,
                    private val notFoundMessage: String = "Topico não Encontrado!"
) {

    fun listar(): List<TopicoView> {
        return repository.findAll().stream().map { t -> topicoViewMapper.map(t)}.collect(Collectors.toList())
    }

    fun buscarPorId(id: Long): TopicoView {
        val topico =  repository.findById(id).orElseThrow{NotFoundException(notFoundMessage)}

        return topicoViewMapper.map(topico)
    }

    fun atualizar(form: AtualizacaoTopicoForm): TopicoView{
        val topico =  repository.findById(form.id).orElseThrow{NotFoundException(notFoundMessage)}

        topico.titulo = form.titulo
        topico.mensagem = form.mensagem
        return topicoViewMapper.map(topico)
    }

    fun cadastrar(form: TopicoDTOForm): TopicoView{
        val topico = topicoFormMapper.map(form)
        repository.save(topico)

        return topicoViewMapper.map(topico)
    }

    fun delete(id: Long) {
        repository.deleteById(id)
    }

}

UsuarioService:

package br.com.virandoprogramador.service

import br.com.virandoprogramador.model.Usuario
import br.com.virandoprogramador.repository.UsuarioRepository
import org.springframework.stereotype.Service

@Service
class UsuarioService(private val repository: UsuarioRepository) {

    fun buscarPorId(id: Long): Usuario {
        return repository.getOne(id)
    }
}

Classes de Controller da Api Rest

Agora, em ultima etapa de código, precisamos criar uma classe de controller, para criar nossos endpoints.

Abaixo vamos criar 5 endpoints utilizando Spring Boot e Kotlin.

Package: Controller.

package br.com.virandoprogramador.controller

import br.com.virandoprogramador.dto.AtualizacaoTopicoForm
import br.com.virandoprogramador.dto.TopicoDTOForm
import br.com.virandoprogramador.dto.TopicoView
import br.com.virandoprogramador.service.TopicoService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
import javax.validation.Valid

@RestController
@RequestMapping("/topico")
class TopicoController (private val service: TopicoService){

    @GetMapping("/listar")
    fun listar(): List<TopicoView>{
        return service.listar()
    }

    @GetMapping("/pesquisar/{id}")
    fun buscarPorId(@PathVariable id: Long): TopicoView{
        return service.buscarPorId(id)
    }

    @PutMapping("/atualizar")
    fun atualizar(@RequestBody @Valid form: AtualizacaoTopicoForm):ResponseEntity<TopicoView>{
        val topicoView = service.atualizar(form)
        return ResponseEntity.ok(topicoView)
    }

    @PostMapping("/salvar")
    fun cadastrar(@RequestBody @Valid form: TopicoDTOForm, uriBuilder: UriComponentsBuilder): ResponseEntity<TopicoView>{
        val topicoView = service.cadastrar(form)
        val uri = uriBuilder.path("/topico/pesquisar/${topicoView.id}").build().toUri()
        return ResponseEntity.created(uri).body(topicoView)
    }

    @DeleteMapping("/delete/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    fun deletar(@PathVariable id: Long){
        service.delete(id)
    }

}

Ufa, terminamos! essa foi a nossa ultima classe de configuração, nela criamos 5 endpoints onde você já pode testar em um front-end ou no Postman.

A classe TopicoController vem com os seguintes endpoints:

  • listar: retorna uma lista de TopicoView
  • buscarPorId: pesquisa por um Id uma entidade de TopicoView
  • atualizar: atualiza algum da entidade TopicoView
  • cadastrar: cria no banco de dados um registro de Topico
  • deletar: deleta por Id

Benefícios da Combinação Kotlin e Spring Boot

Interoperabilidade:

Kotlin é totalmente interoperável com o Java, permitindo que desenvolvedores utilizem bibliotecas Java existentes, incluindo o ecossistema do Spring Boot, sem problemas.

Isso simplifica a migração de projetos Java para Kotlin e oferece flexibilidade para aproveitar as vantagens de ambas as linguagens.

Produtividade Aprimorada:

O Spring Boot, com seu modelo de programação baseado em convenções e configuração automática, complementa perfeitamente a filosofia de Kotlin de reduzir a verbosidade do código.

Isso permite que os desenvolvedores se concentrem mais na lógica de negócios e menos na configuração do framework.

Ecossistema Abundante:

Tanto Kotlin quanto Spring Boot possuem ecossistemas robustos de ferramentas, bibliotecas e recursos de suporte.

Isso significa que os desenvolvedores têm acesso a uma ampla gama de recursos para acelerar o desenvolvimento e resolver problemas comuns de maneira eficiente.

Conclusão sobre Kotlin com Spring Boot

Neste artigo, aprendemos como criar uma API RESTful utilizando Kotlin e Spring Boot. Exploramos a configuração inicial do projeto, a implementação de entidades, repositórios e controladores, bem como a execução da aplicação.

Espero que este guia tenha sido útil para você começar a desenvolver suas próprias APIs com Kotlin e Spring Boot.

Avalie o Conteúdo
Compartilhar:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *