Introdução: por que Kotlin para microsserviços?
O desenvolvimento moderno de backend está cada vez mais se afastando de monolitos em favor da arquitetura de microsserviços. Kotlin, sendo uma linguagem estaticamente tipada na JVM, oferece uma combinação única de concisão, segurança e desempenho. Neste artigo, abordaremos técnicas avançadas de construção de microsserviços em Kotlin: desde a escolha do framework até o deploy em Docker.
Vamos nos concentrar em aspectos práticos: configuração de endpoints reativos, injeção de dependências sem Spring, processamento assíncrono com corrotinas e escrita de testes. Se você já conhece os fundamentos do Kotlin e quer passar para o próximo nível, este material é para você.
1. Arquitetura de microsserviço com Ktor
Escolha do framework: Ktor vs Spring Boot
Para o nível avançado, escolhemos o Ktor — um framework assíncrono e leve da JetBrains. Diferente do Spring Boot, o Ktor não usa reflexão ou anotações no estilo IoC, o que dá controle total sobre o contexto da aplicação. Esta é a escolha ideal para microsserviços onde a velocidade de inicialização e o baixo consumo de memória são importantes.
Estrutura do projeto
Estrutura recomendada para um microsserviço com Ktor:
project/├── src/│ ├── main/kotlin/com/exemplo/│ │ ├── Application.kt // Ponto de entrada│ │ ├── routes/ // Rotas (REST API)│ │ ├── services/ // Lógica de negócios│ │ ├── repositories/ // Acesso a dados│ │ ├── models/ // DTOs e entidades│ │ └── di/ // Módulos de injeção de dependências│ └── test/kotlin/com/exemplo/ // Testes└── build.gradle.ktsConfiguração de rotas
Exemplo de endpoint REST reativo usando corrotinas:
fun Application.configureRouting() { routing { route("/api/v1/users") { get("/{id}") { val id = call.parameters["id"]?.toIntOrNull() ?: return@get call.respondText("ID inválido", status = HttpStatusCode.BadRequest) val user = userService.getUserById(id) if (user != null) { call.respond(user) } else { call.respondText("Usuário não encontrado", status = HttpStatusCode.NotFound) } } post { val user = call.receive<UserDto>() val created = userService.createUser(user) call.respond(HttpStatusCode.Created, created) } } }}2. Injeção de dependências com Koin
Por que Koin?
Koin é um DSL para injeção de dependências, perfeitamente compatível com Kotlin. Ele não requer geração de código ou reflexão, funcionando no nível de construtores e expressões lambda. Isso permite criar serviços modulares e testáveis.
Exemplo de módulo DI
val appModule = module { single { DatabaseConnection() } single { UserRepository(get()) } factory { UserService(get()) }}
fun Application.configureDI() { startKoin { modules(appModule) }}Observe: usamos factory para serviços — cada requisição recebe uma nova instância, o que é importante para cenários reativos. single é usado para repositórios e conexões com banco de dados.
3. Assincronia e corrotinas
Reactive Streams e Flow
Para trabalhar com fluxos de dados em tempo real, use kotlinx.coroutines.flow. É um análogo ao Reactive Streams, mas com sintaxe mais simples e suporte completo a corrotinas.
Exemplo de serviço que processa dados de uma fila:
class OrderProcessor(private val orderRepository: OrderRepository) {
suspend fun processOrders(): Flow<OrderResult> = flow { orderRepository.getPendingOrders().coll