Introducción: por qué Kotlin es el lenguaje ideal para microservicios
El desarrollo moderno de backend se aleja cada vez más de las soluciones monolíticas en favor de la arquitectura de microservicios. Kotlin, al ser un lenguaje de tipado estático que funciona sobre la JVM, ofrece a los desarrolladores una combinación única de concisión, seguridad y rendimiento. A diferencia de Java, Kotlin permite escribir código más expresivo y seguro sin el boilerplate repetitivo, lo cual es crítico al construir sistemas distribuidos complejos.
En este artículo analizaremos el ciclo completo de creación de un microservicio en Kotlin: desde la elección del framework hasta el despliegue en un contenedor. Aprenderás a usar Ktor para el servidor HTTP, Koin para la inyección de dependencias y Exposed para trabajar con la base de datos. Todos los ejemplos de código están listos para usar en proyectos reales.
1. Diseño de la arquitectura del microservicio
El primer paso es definir los límites del servicio. A diferencia de un monolito, cada microservicio se encarga de una capacidad de negocio. Consideremos el ejemplo de un servicio de gestión de usuarios (User Service).
Estructura del proyecto
La estructura recomendada para un microservicio en Kotlin es:
user-service/├── src/│ ├── main/kotlin/com/example/userservice/│ │ ├── application/ # Capa de aplicación (casos de uso)│ │ ├── domain/ # Modelos de dominio│ │ ├── infrastructure/ # Repositorios, BD, APIs externas│ │ └── presentation/ # Controladores (rutas de Ktor)│ └── resources/│ └── application.conf└── build.gradle.ktsEsta arquitectura sigue los principios de Clean Architecture y Domain-Driven Design, lo que facilita las pruebas y el mantenimiento.
Selección del stack tecnológico
- Framework HTTP: Ktor (ligero, asíncrono, corrutinas)
- DI: Koin (simple, sin reflexión)
- ORM: Exposed (DSL nativo de Kotlin para SQL)
- Base de datos: PostgreSQL
- Contenedorización: Docker + Docker Compose
2. Implementación del microservicio en Kotlin con Ktor y Koin
Configuración de build.gradle.kts
Agregamos las dependencias necesarias:
plugins { kotlin("jvm") version "1.9.22" id("io.ktor.plugin") version "2.3.7" kotlin("plugin.serialization") version "1.9.22"}
dependencies { implementation("io.ktor:ktor-server-core:2.3.7") implementation("io.ktor:ktor-server-netty:2.3.7") implementation("io.ktor:ktor-server-content-negotiation:2.3.7") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7") implementation("org.jetbrains.exposed:exposed-core:0.44.1") implementation("org.jetbrains.exposed:exposed-dao:0.44.1") implementation("org.jetbrains.exposed:exposed-jdbc:0.44.1") implementation("org.postgresql:postgresql:42.6.0") implementation("io.insert-koin:koin-ktor:3.5.0")}Creación del modelo de dominio y el repositorio
// domain/User.ktdata class User( val id: Long, val name: String, val email: String)
// infrastructure/UserRepository.ktinterface UserRepository { suspend fun getAll(): List<User> suspend fun getById(id: Long): User? suspend fun create(user: User): User}Implementación del repositorio con Exposed
// infrastructure/UserRepositoryImpl.ktimport org.jetbrains.exposed.sql.*import org.jetbrains.exposed.sql.transactions.transaction
object UsersTable : Table("users") { val id = long("id").autoIncrement() val name = varchar("name", 255) val email = varchar("email", 255) override val primaryKey = PrimaryKey(id)}
class UserRepositoryImpl(private val database: Database) : UserRepository { override suspend fun getAll(): List<User> =