Do Java ao Kotlin com Spring Boot

Nesse artigo irei mostrar como migrei o código do meu currículo on-line para Kotlin e como foi a experiência.

A linguagem Kotlin (cot-lin) sempre me atraiu assim como o Go, ambas já foram mostradas aqui no blog em Linguagens da JVM: Kotlin e Aprendendo GO/GOLANG, mas o Kotlin me atraiu mais pelas possibilidades que possui, como ser compilável para Java, Javascript, Android e Nativo além de ter suporte a vários paradigmas de programação.

A mudança é a lei da vida. E aqueles que apenas olham para o passado ou para o presente irão com certeza perder o futuro.

John Kennedy

O projeto

Fiz uma modelagem simples baseado nas informações que seriam importantes tornar dinâmicas:

Diagrama do Site Currículo
Diagrama do Site Currículo

Minha ideia foi produzir o mais rápido possível, por isso utilizei Spring Initalizr para configurar o projeto com Spring Data JPA, Web, Devtools etc. O principal foi selecionar o Kotlin como linguagem:

Configuração do Spring Initialzr
Configuração do Spring Initialzr

Minha ideia era, além da mudança de tecnologia, adicionar as funcionalidades:

  • possibilitar o cadastro dinâmico de informações;
  • fornecer uma API REST para as informações;

Para as próximas versões tenho o desejo de criar a interface para administras as informações do site e suporte a webhooks.

Mão na massa

Logo ao criar as entidades já da para perceber um ganho na redução de código, parecido quando se utiliza Lombok só que com menos anotações. Criei as entidades JPA como Data Classes, pois esse formato já me atendia:

@Entity
data class Curriculo(
        @Id
        var id: Long = 0L,

        @Column(length = 100, nullable = false, name = "PRIMEIRO_NOME")
        var primeiroNome: String = "",

        @Column(length = 100, nullable = false, name = "ULTIMO_NOME")
        var ultimoNome: String = ""
) {
    @Column(length = 240, nullable = false)
    var resumo: String = "";

    @Column(length = 240, nullable = false)
    var profissao: String = ""

    @OneToMany(cascade = [CascadeType.ALL])
    var historicos: List<Historico>? = null

    @OneToMany(cascade = [CascadeType.ALL])
    var contatos: List<Contato>? = null

    @OneToMany(cascade = [CascadeType.ALL])
    var conhecimentos: List<Conhecimento>? = null
}

Nesse código de exemplo não utilizei o Null Saffety e não declarei as propriedades com val, mas aproveitei a sintaxe primary constructor e a inferência dos getters e setters (em tempo de compilação), isso seguiu para todas as entidades do projeto.

Uma curiosidade: em Kotlin a classe pública não precisa estar em um arquivo individual com o nome igual o da mesma (como em Java), bem como funções, por isso pode-se escolher entre reduzir o número de arquivos no projeto ou dividir o código em muitos. Particularmente prefiro ter um arquivo separando o propósito da classe, mas pode ser uma boa alternativa para pequenos projetos. Durante os estudos achei no Github um projeto de exemplo que agrupou tudo em um arquivo.

Quando no projeto o repositório no Spring Data não precisa de query methods específicos, basicamente você tem uma interface vazia que em Java são poucas linhas. No Kotlin ainda é possível remover os brackets:

package dev.ivanqueiroz.curriculo.dominio.curriculo

import org.springframework.data.jpa.repository.JpaRepository

interface CurriculoRepository: JpaRepository<Curriculo, Long>

Como ainda não criei um módulo de administração criei uma classe para popular as informações do banco, a classe implementa CommandLineRunner do Spring para realizar operações quando a aplicação inicializa.

@Component
@ConditionalOnProperty(prefix = "banco", name = ["inicializar"], havingValue = "true")
class DbInitializer : CommandLineRunner {

    @Autowired
    lateinit var curriculoRepositorio: CurriculoRepository;

    @Autowired
    lateinit var historicoRepository: HistoricoRepository

    @Autowired
    lateinit var conhecimentoRepository: ConhecimentoRepository

    @Autowired
    lateinit var contatoRepository: ContatoRepository

    override fun run(vararg args: String?) {

        contatoRepository.deleteAll()
        conhecimentoRepository.deleteAll()
        historicoRepository.deleteAll()
        curriculoRepositorio.deleteAll()
        val curriculo = preencheCurriculo()
        curriculoRepositorio.save(curriculo)
    }
    .
    .
    .

Aqui a sintaxe da linguagem também me mostrou algumas coisas interessantes como a declaração de utilização da interface, que sai a palavra implements e entra apenas o : no lugar. Como as propriedades vão ser inicializadas pelo injetor do Spring, existe a palavra lateinit que informa ao compilador que aquela propriedade vai ser inicializada um pouco mais tarde (late), impedido que reclame de propriedade não inicializada.

Para a API REST adicionei o suporte HATEOAS do Spring Boot no Maven para agilizar o desenvolvimento. Devido a forma que criei meus modelos, precisei modificar as informações publicadas na API, para isso criei novas classes estendendo de ResourceSupport (Spring) que expõem somente os dados que desejamos:

class ConhecimentoResource @JsonCreator constructor(@JsonIgnore val conhecimento: Conhecimento) : ResourceSupport() {

    val id: Long = conhecimento.id

    @JsonProperty("assunto")
    val assunto: String = conhecimento.titulo

    @JsonProperty("valorNivel")
    val valorNivel: Float = conhecimento.nivel

    @JsonProperty("descricaoNivel")
    var descricaoNivel: String = ""
        get() {
            if(conhecimento.tipoConhecimento == TipoConhecimento.ESPECIFICO) {
                return if (conhecimento.nivel < 0.25f) {
                    "Iniciante"
                } else if (0.25f >= conhecimento.nivel && conhecimento.nivel < 0.75f) {
                    "Proficiente"
                } else if (conhecimento.nivel >= 0.75f || conhecimento.nivel < 1.0f) {
                    "Especialista"
                } else if (conhecimento.nivel == 1.0f) {
                    "Mestre"
                } else {
                    "Iniciante"
                }
            }else{
                return if (conhecimento.nivel < 0.25f) {
                    "Instrumental"
                } else if (0.25f >= conhecimento.nivel && conhecimento.nivel < 0.75f) {
                    "Básico"
                } else if (conhecimento.nivel >= 0.75f || conhecimento.nivel < 1.0f) {
                    "Intermediário"
                } else if (conhecimento.nivel == 1.0f) {
                    "Avançado"
                } else {
                    "Instrumental"
                }
            }
        }

    init {
        add(linkTo(methodOn(ConhecimentoRestController::class.java).conhecimento(id)).withSelfRel())
    }
}

Nesse trecho a classe recebe pelo construtor o objeto que contém as informações, transformando ele para a representação desejada. O trecho com init é para a configuração do hiperlink que representa o ConhecimentoResource do padrão HATOAS. Sobrescrevi o get da propriedade de descrição do nível com uma lógica que calcula o nível baseado no tipo do conhecimento.

Mais uma vez os recursos da linguagem me pouparam algumas linhas, no momento que fui escrever a classe ConhecimentoService e o método que obtém todos os conhecimentos cadastrados. Utilizando streams para transformar o retorno o código ficou bem simples:

fun obterTodosConhecimentos(): List<ConhecimentoResource> {
        return conhecimentoRepository.findAll().map { c -> ConhecimentoResource(c) }
}

Apesar de ser um pequeno detalhe pra mim fez diferença a não necessidade de utilizar a palavra new para instanciar objeto em Kotlin, não sei explicar mas me pareceu muito mais simples.

Concluíndo

Achei a experiência de substituir o Java por Kotlin surpreendentemente praseroza! Foi bem natural e muitas vezes intuitiva, claro que o suporte da IDE do IntelliJ IDEA ajudou muito nos momentos que precisei saber de alguma sintaxe (através da ferramenta de conversão de Java para Kotlin, também disponível on-line), mas quando não ajudava a documentação me guiava com vários exemplos. Nem tudo para mim foram flores, algumas sintaxes achei bem estranhas como a que é para sobrescrever o getters e setters e o lateinit, o fato de tudo ser public por padrão me deixou um pouco confuso se isso é bom (pode ser só implicância minha). Outro ponto é que fora a documentação não há muitos locais que ensinem a linguagem sem atrelar ao Android, eu até entendo mas vejo tanto potencial além disso.

No geral o Kotlin tem mais vantagens do que desvantagens para mim, eu sugiro para você que chegou até aqui que dê uma chance e experimente, além da documentação indico o livro Kotlin em Ação, está me ajudando muito nesse novo conhecimento.

Quase ia esquecendo, o código do meu projeto está nesse link. Espero que ajude em seus estudos!

Um abraço e até o próximo artigo!

Escrito em 26 Abril de 2019