Pular para o conteúdo

Currículo de um desenvolvedor Kotlin

O que se espera de um engenheiro Kotlin no backend: a linguagem e a plataforma JVM, Spring Boot, persistência, concorrência e operação.

Publicada em · 17 min de leitura

Tamanho da fonte:
Largura da coluna:
Tom:
Espaço entre linhas:
Fonte:

Kotlin chegou ao backend pelo JVM. Ele roda sobre o mesmo ecossistema do Java (Spring Boot, Hibernate, Micrometer, Gradle), e por isso um engenheiro Kotlin precisa dominar duas coisas ao mesmo tempo: a linguagem em si e a plataforma que ela usa por baixo. A sintaxe mais enxuta, a null safety verificada pelo compilador e as coroutines são os motivos mais citados para a adoção. Em muitas equipes, o Kotlin já é a linguagem padrão de serviços novos, mesmo quando o legado continua em Java.

A sintaxe se aprende em semanas. O que leva mais tempo é o idiomatismo, ou seja, escrever o que a linguagem oferece e não o que se trazia de outra. Quem traz hábitos de Java escreve código que compila, mas não aproveita a linguagem: usa !! em todo lugar, bloqueia threads dentro de coroutines, cria GlobalScope para “resolver” escopo e trata data class como entidade de banco sem pensar no que acontece com equals e hashCode. Esse é o equivalente do “Java em Go” que a trilha de Go aponta como red flag. Este currículo existe para evitar esses vícios desde o começo.

Esta nota define o que se espera de um engenheiro Kotlin backend em processos seletivos e no dia a dia de uma equipe, organizado em fases que se apoiam umas nas outras. A stack de referência é a do projeto: Spring Boot com Gradle, com starters em todos os serviços, PostgreSQL com Liquibase, MongoDB e Azure Service Bus para mensageria. A ordem importa: sem entender null safety e coroutines, a parte de APIs vira cópia de exemplo sem compreensão.


O mapa do currículo

FaseDomínio técnicoHabilidades centraisEntrega prática recomendada
1Kotlin idiomático e sistema de tiposNull safety, data class, sealed, when exaustivo, funções de escopo, interop com JavaModelo de domínio de pedidos com estados sealed e nenhum !!
2Orientação a objetos, genéricos e coleçõesDelegação, variância, reified, coleções imutáveis, Sequence, tratamento funcional de errosLeitor de arquivos grandes em streaming, sem carregar tudo em memória
3Internals da JVM e performanceBytecode, heap e GC, JIT, boxing, startup, profiling com JFRDiagnóstico de alocação excessiva em um serviço com carga simulada
4Coroutines e concorrência estruturadasuspend, CoroutineScope, Dispatchers, cancelamento, Flow, Channel, MutexWorker com limite de concorrência, timeout e cancelamento limpo
5APIs, frameworks e mensageriaSpring Boot com starters, validação, ProblemDetail, JWT, Azure Service BusAPI REST com erros padronizados, autenticação e consumidor de mensagens
6Persistência e dadosJDBC, HikariCP, JPA/Hibernate, N+1, Liquibase, Spring Data MongoDBRepositório Postgres com changelogs versionados e prova de ausência de N+1
7Arquitetura, testes e operaçãoGradle multi-módulo, hexagonal, Kotest, MockK, Testcontainers, Micrometer, OpenTelemetry, DockerServiço completo com testes de integração, métricas e imagem enxuta

Os 7 pilares técnicos fundamentais

1. Kotlin idiomático e sistema de tipos

A maior parte do valor de Kotlin está em tornar erros de programação visíveis para o compilador. Um engenheiro que domina a linguagem usa o sistema de tipos como primeira linha de defesa contra erros.

Competências exigidas:

  • Null safety: distinguir tipos nuláveis (String?) de não nuláveis, usar ?., ?: e let com intenção, e entender por que !! é um sinal de modelagem fraca. Saber também como o compilador trata tipos vindos do Java (os chamados platform types), que podem ser nulos sem aviso.
  • Tipos de dados: data class para transportar valores, enum class para conjuntos fechados, sealed class e sealed interface para modelar estados, com when exaustivo que dispensa o else e faz o compilador avisar quando um caso novo é adicionado.
  • value class para envolver tipos primitivos (um OrderId que não pode ser confundido com um UserId) sem custo de alocação na maior parte dos casos.
  • Funções de escopo (let, run, apply, also, with): saber qual escolher pelo papel que a função tem, transformar, configurar ou observar, e reconhecer quando o encadeamento deixa o código menos legível.
  • Expressões em vez de instruções: if, when e try produzem valores, e um engenheiro idiomático os usa para inicializar variáveis com val.
  • Interop com Java: @JvmStatic, @JvmOverloads, como a classe Optional e as exceções verificadas do Java aparecem no Kotlin e como expor APIs Kotlin de forma amigável para quem ainda chama pelo Java.

Entrega sugerida: modelar um fluxo de pagamento com estados Pending, Approved, Declined e Refunded usando sealed interface, sem nenhum !! e com um when que cubra todos os casos.

2. Orientação a objetos, genéricos e coleções

Kotlin mantém a orientação a objetos do JVM, mas incentiva composição, imutabilidade e funções de ordem superior. Um engenheiro sênior sabe quando cada estilo serve.

Competências exigidas:

  • Interfaces com implementação padrão e delegação com by, usadas para compor comportamento sem herança profunda.
  • Classes abertas exigem decisão consciente: classes são final por padrão. Saber quando liberar com open e por que frameworks como Spring e Hibernate dependem de classes e métodos não finais (o que os plugins kotlin-spring e kotlin-jpa resolvem).
  • Variância de genéricos (in e out), a diferença entre List<out T> e MutableList<T>, e funções inline com parâmetros reified para acessar o tipo em tempo de execução.
  • Coleções imutáveis por padrão. List é somente leitura; MutableList é a exceção. Saber a diferença entre List e Sequence: List avalia cada etapa de uma vez e aloca coleções intermediárias, enquanto Sequence processa elemento por elemento.
  • Tratamento de erros funcional: runCatching, Result e, quando o projeto já usa, a biblioteca Arrow com Either. Saber também quando exceções continuam sendo a escolha certa, como em erros de infraestrutura.
  • Custo de lambdas: entender que funções inline eliminam a alocação do lambda, e quando isso importa de fato.

Entrega sugerida: uma biblioteca que lê CSV com Sequence a partir de um BufferedReader, com teste que comprova o processamento em streaming em um arquivo de vários gigabytes.

3. Internals da JVM e performance

Um desenvolvedor que escreve Kotlin para servidores precisa entender o que o compilador gera e o que a máquina virtual faz com esse código. Sem isso, diagnósticos de latência e memória viram tentativa e erro.

Competências exigidas:

  • Kotlin compila para bytecode JVM. Saber ler o resultado com javap e com o decompilador do IntelliJ, e identificar o que vira objeto, o que vira método estático e o que é gerado para propriedades e lambdas.
  • Modelo de memória: heap, pilha de threads e a diferença entre objetos alocados e objetos que sobrevivem a uma coleta. Entender como a análise de escape do compilador JIT (C2) remove alocações quando consegue provar que um objeto não sai do método.
  • Garbage collector: G1 como padrão, ZGC e Shenandoah para baixa latência, e como ler pausas em logs de GC. Saber que a escolha de coletor muda o perfil de latência, não o volume de trabalho.
  • Boxing: Int pode virar java.lang.Integer em coleções genéricas e em tipos nuláveis, o que cria alocações escondidas em laços quentes.
  • Startup e footprint: o custo de carregamento de classes e de inicialização do Spring em contêineres e serverless, e como reduzi-lo com CDS, AOT e GraalVM Native Image, com as trocas que cada opção impõe.
  • Profiling com JFR (Java Flight Recorder), async-profiler e análise de heap dump com o Eclipse MAT ou o JDK Mission Control.

Entrega sugerida: um serviço com alocação excessiva em um endpoint quente, diagnosticado com JFR, corrigido e medido antes e depois com um benchmark simples de JMH.

4. Coroutines e concorrência estruturada

Este é o pilar que mais separa o engenheiro Kotlin júnior do sênior no backend. Coroutines são uma forma de escrever código assíncrono como se fosse sequencial, mas o modelo só funciona bem quando o engenheiro entende o ciclo de vida que ele cria.

Competências exigidas:

  • Funções suspend e a transformação que o compilador faz em máquina de estados (continuation). Saber que uma função suspend não cria thread por si só.
  • CoroutineScope e concorrência estruturada: um pai espera pelos filhos e propaga cancelamento e exceções para cima. Diferença entre coroutineScope (falha tudo quando um filho falha) e supervisorScope (isola falhas entre filhos).
  • Dispatchers: Default para CPU, IO para bloqueio, e limitedParallelism para controlar concorrência. Nunca bloquear Dispatchers.Default, porque isso trava os poucos threads que ele tem.
  • Cancelamento cooperativo: uma coroutine só para em um ponto de suspensão ou quando verifica isActive ou chama ensureActive(). Engolir CancellationException em um catch genérico quebra o cancelamento, e finally com withContext(NonCancellable) é o caminho para limpeza.
  • Flow frio (cada coletor recebe a sequência desde o início) e fluxos quentes (StateFlow, SharedFlow). Saber quando usar Channel para comunicar produtores e consumidores e quando Mutex e Semaphore são a ferramenta certa para proteger um recurso.
  • Virtual threads do Java 21 como alternativa ao modelo de coroutines em código bloqueante. Saber onde cada um se encaixa e que os dois podem coexistir no mesmo serviço.
  • Testes de coroutines com runTest e tempo virtual, sem Thread.sleep e sem testes intermitentes.

Erros que um revisor procura de imediato: GlobalScope para tarefas que deveriam pertencer a uma requisição, runBlocking dentro de um handler HTTP, chamadas JDBC bloqueantes dentro de Dispatchers.Default e launch sem nenhum tratamento de exceção.

Entrega sugerida: um worker que consome uma fila, limita a concorrência com um Semaphore, aplica timeout por tarefa e termina sem deixar coroutines órfãs, com testes em runTest.

5. APIs, frameworks e comunicação

Na stack do projeto, o framework é o Spring Boot, e todos os serviços partem dos starters. O foco desta fase é usar bem o que a equipe já adotou: entender o que cada starter configura, como o Spring conecta os componentes e onde ele esconde custos que precisam ser conhecidos.

Competências exigidas:

  • Spring Boot 3 ou superior com Gradle: starters como base de cada serviço, configuração por ambiente com application.yml, profiles e @ConfigurationProperties.
  • Controllers, injeção de dependências, validação com Bean Validation e tratamento centralizado de erros.
  • Erros padronizados com ProblemDetail (RFC 9457) em vez de respostas improvisadas. Saber mapear exceções de domínio para status HTTP sem vazar detalhes internos.
  • Controllers suspend no Spring MVC e a diferença entre Spring MVC com coroutines e Spring WebFlux, que tem seu próprio modelo reativo.
  • Serialização com Jackson e o módulo Kotlin, que é o padrão do Spring. Saber como ele trata valores nulos e campos ausentes, e quando kotlinx.serialization é usado em partes do código que não são Spring.
  • Segurança com Spring Security: resource server com JWT, validação de claims e autorização por método.
  • Documentação com OpenAPI pelo springdoc, WebSockets e Server-Sent Events para streaming.
  • Mensageria com Azure Service Bus: filas e tópicos com assinaturas, modos de recebimento (peek lock e receive and delete), finalização explícita das mensagens, dead letter queue e reprocessamento. O consumidor precisa ser idempotente, porque o Service Bus entrega cada mensagem pelo menos uma vez.
  • Idempotência como regra de projeto: chave de deduplicação, registro do processamento no banco e comportamento previsível quando a mesma mensagem chega duas vezes.

Entrega sugerida: uma API REST de pedidos com validação, erros em ProblemDetail, autenticação JWT e um consumidor do Azure Service Bus que ignora mensagens já processadas.

6. Persistência e dados

Serviços Kotlin no backend passam a maior parte do tempo falando com banco de dados. Saber o que cada camada faz por baixo evita problemas que só aparecem em produção.

Competências exigidas:

  • JDBC e HikariCP: tamanho do pool, tempo máximo de espera, detecção de vazamento de conexão e a relação entre threads, conexões e o limite do banco.
  • JPA com Hibernate: carregamento lazy, o problema N+1 e as soluções com fetch join e @EntityGraph, e o controle de transações com @Transactional e seu proxy.
  • Entidades JPA e data class: uma data class gera equals, hashCode e toString a partir de todos os campos, o que pode disparar carregamento lazy e recursão em relacionamentos bidirecionais. Entidades pedem class comum com identidade explícita.
  • Migrations com Liquibase: changelogs, changesets, identificadores únicos, checksums e o cuidado de nunca editar um changeset já aplicado em ambientes compartilhados. Saber também usar contexts para separar dados de teste de dados de produção.
  • MongoDB com Spring Data MongoDB: documentos, @Document, MongoTemplate para consultas complexas e a diferença de modelagem em relação ao relacional. Embutir ou referenciar documentos é uma decisão que depende de como os dados são lidos e atualizados.
  • Índices no MongoDB e análise de consultas com explain, para não deixar listagens fazendo varredura completa da coleção.
  • Transações no MongoDB, que exigem replica set, e a escolha consciente entre o que fica no Postgres e o que fica no MongoDB em um mesmo serviço.
  • Cache com Spring Cache, com política de expiração e invalidação consistente.

Entrega sugerida: um repositório Postgres com changelogs do Liquibase e um teste de integração que comprova, pelo log de SQL, que a listagem de pedidos executa uma única consulta. Em seguida, um repositório MongoDB com índices definidos e uma consulta verificada com explain.

7. Arquitetura, testes e operação

Um serviço que vai durar anos precisa de estrutura clara, testes que confiam no que testam e observabilidade desde o primeiro dia.

Competências exigidas:

  • Gradle com Kotlin DSL, projetos multi-módulo e catálogo de versões (libs.versions.toml).
  • Arquitetura hexagonal ou Clean Architecture com domínio sem dependência de framework, verificada por regras automáticas com ArchUnit.
  • Testes com JUnit 5, Kotest para asserções e testes de propriedade, MockK para dublês em Kotlin (que lida melhor com final e funções de extensão do que o Mockito) e Testcontainers para Postgres e MongoDB reais. Para o Azure Service Bus, o teste de integração precisa de uma estratégia própria, seja um dublê da fila ou um ambiente de teste dedicado.
  • Qualidade estática com ktlint, detekt e cobertura com Kover. A meta de cobertura importa menos do que o que os testes realmente verificam.
  • Observabilidade com Micrometer para métricas, OpenTelemetry para traces e logs estruturados em JSON com SLF4J e Logback.
  • Resiliência com Resilience4j: circuit breaker, retry com backoff exponencial e jitter, e limites de concorrência por dependência.
  • Empacotamento: imagens com JRE mínima, jlink, camadas do Spring Boot para acelerar builds de contêiner e, quando faz sentido, GraalVM Native Image.

Entrega sugerida: um serviço completo com testes de integração contra Postgres e MongoDB reais via Testcontainers, métricas expostas, logs estruturados e uma imagem Docker de tamanho controlado.


Roteiro prático de aprendizado (4 a 6 meses)

Para quem já programa em backend, o cronograma abaixo leva ao nível pleno e aponta o caminho para o sênior. Quem vem de Java pode acelerar a fase 1, mas não deve pular a 4.

Mês 1: fundamentos da linguagem. Reescreva um serviço Java simples em Kotlin, sem !!, com data class, sealed e when exaustivo. Ao final, peça a alguém que revise a diferença entre o código antigo e o novo.

Mês 2: coleções, genéricos e JVM. Implemente o leitor de CSV com Sequence, leia o bytecode gerado por algumas funções e rode um profiling simples com JFR para ver alocações reais.

Mês 3: coroutines. Construa o worker com limite de concorrência, timeout e cancelamento, e escreva os testes com runTest. Refaça o mesmo worker com Flow e compare.

Mês 4: APIs e persistência. Monte a API de pedidos com Spring Boot, validação, ProblemDetail, JWT, Postgres com Liquibase e um repositório MongoDB com índices. Provoque um N+1 de propósito, observe no log e corrija.

Mês 5: mensageria e resiliência. Adicione um consumidor do Azure Service Bus idempotente e um cliente HTTP com circuit breaker e retry com jitter. Simule a queda de uma dependência e observe o comportamento.

Mês 6: operação. Adicione métricas, traces, logs estruturados e Testcontainers nos testes de integração. Gere uma imagem Docker com camadas e meça o tamanho.


Portfólio técnico: o que chama a atenção em vagas

Recrutadores descartam CRUDs sem regra de negócio e projetos que só mostram o tutorial do framework. Um repositório que demonstre engenharia de verdade tem mais peso:

Projeto 1: serviço de pedidos com máquina de estados. Modelagem com sealed interface, transições validadas no domínio, persistência com Postgres e Liquibase, eventos publicados no Azure Service Bus e testes de transição com Kotest. O diferencial está em mostrar que estados inválidos não compilam.

Projeto 2: pipeline de ingestão de eventos. Consumidor do Azure Service Bus com concorrência controlada por coroutines, idempotência, dead letter queue e métricas de atraso, gravando o resultado em MongoDB. O diferencial é um teste de carga com resultado documentado e um gráfico de GC antes e depois de uma otimização.

Projeto 3: biblioteca ou CLI de processamento de arquivos. Leitura em streaming com Sequence, benchmark com JMH e relatório de alocações. Serve para provar domínio de linguagem e de JVM ao mesmo tempo.


O que os tech leads procuram e sinais de alerta

Em entrevistas e revisões de código, o que se avalia é se a pessoa escreve Kotlin ou Java com sintaxe de Kotlin.

Sinais positivos: modelagem com tipos que impedem estados inválidos, cancelamento tratado como parte do fluxo, escolha consciente entre Flow, Channel e Mutex, e respostas de erro previsíveis para quem consome a API.

Sinais de alerta que causam descarte:

  1. !! espalhado pelo código, usado para silenciar o compilador em vez de modelar o caso nulo.
  2. GlobalScope ou runBlocking em código de produção, principalmente dentro de handlers HTTP.
  3. Chamadas bloqueantes dentro de Dispatchers.Default, o que trava a CPU inteira do serviço.
  4. catch (e: Exception) que engole CancellationException e quebra o cancelamento.
  5. data class como entidade JPA, com equals, hashCode e toString que disparam carregamento lazy ou recursão.
  6. Testes com Thread.sleep para esperar coroutines, em vez de runTest e tempo virtual.

Leituras canônicas e referências essenciais

Livros

  • Kotlin in Action, por Dmitry Jemerov e Svetlana Isakova (Manning). Base sólida da linguagem, escrita pelos próprios projetistas.
  • Effective Kotlin, por Marcin Moskała. Recomendações práticas de idiomatismo, uma leitura para depois do primeiro mês.
  • Kotlin Coroutines: Deep Dive, por Marcin Moskała. Aprofundamento no modelo de coroutines, com foco em cancelamento e concorrência estruturada.
  • Spring in Action, por Craig Walls (Manning). Referência do Spring Boot, com exemplos de APIs e persistência.
  • Java Concurrency in Practice, por Brian Goetz. Não é sobre Kotlin, mas explica o modelo de threads, visibilidade de memória e sincronização sobre o qual as coroutines rodam.

Documentação e recursos

  • Documentação oficial do Kotlin em kotlinlang.org/docs, com a seção de coroutines e o guia de null safety.
  • Koans oficiais do Kotlin, para praticar a linguagem com exercícios guiados.
  • Guias oficiais do Spring em spring.io/guides, especialmente os de Kotlin e de Spring Data, incluindo Spring Data MongoDB.
  • Documentação oficial do Liquibase, com a referência de changesets e de rollback, e da Azure Service Bus, com a parte de sessões, filas mortas e modos de recebimento.
  • Blog oficial do Kotlin, que documenta as mudanças da linguagem, incluindo o compilador K2 usado por padrão desde o Kotlin 2.0.

Para aprofundar os conceitos de backend que este currículo cobre e que já têm nota no vault, consulte as notas de Go em Curriculo de um Desenvolvedor Go e Trilha de Go, que tratam de concorrência, persistência e observabilidade de forma comparável. A trilha de Kotlin correspondente está em Trilha de Kotlin.

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?