Por qué DDD y Kotlin son la pareja ideal
Cuando un proyecto crece y los requisitos de negocio comienzan a parecerse a una maraña enredada, el enfoque CRUD habitual deja de funcionar. Te sorprendes pensando que la mitad del código son interminables comprobaciones if-else, y cualquier cambio en la lógica conlleva modificar una docena de archivos. ¿Te suena familiar?
Aquí entra en escena Domain-Driven Design (DDD), un enfoque que sitúa la lógica de negocio en el centro del universo. Y Kotlin, con su sintaxis concisa, su potente sistema de tipos y su soporte para conceptos funcionales, se convierte en la herramienta ideal para implementar DDD. En este artículo, analizaremos cómo combinar estas dos fuerzas para que tu código no solo funcione, sino que sea expresivo y flexible.
Si escribes en Kotlin y quieres que tu arquitectura respire en lugar de crujir, has llegado al lugar indicado. Hoy no solo hablaremos de teoría, sino que escribiremos ejemplos de código reales.
Domain-Driven Design: un breve resumen para los ocupados
Antes de sumergirnos en el código, pongámonos de acuerdo en los términos. DDD no es un framework ni una librería. Es un conjunto de principios que ayudan a los desarrolladores y al negocio a hablar el mismo idioma.
Conceptos clave:
- Ubiquitous Language (Lenguaje ubicuo) — los términos del código deben coincidir con los términos que utilizan los analistas de negocio y los clientes.
- Bounded Context (Contexto delimitado) — un límite claro dentro del cual el modelo tiene sentido. Por ejemplo, "Cliente" en el contexto de ventas y "Cliente" en el contexto de soporte son entidades diferentes.
- Entity (Entidad) — un objeto con un identificador único que cambia con el tiempo.
- Value Object (Objeto de valor) — un objeto inmutable que se define por sus atributos, no por su identificador.
- Aggregate (Agregado) — un grupo de objetos relacionados que se considera como una unidad.
- Repository (Repositorio) — una abstracción para acceder a los datos que oculta los detalles de almacenamiento.
¿Suena abstracto? Ahora lo traduciremos a Kotlin.
Value Objects en Kotlin: elegancia y seguridad
Empecemos por lo más agradable: los Value Objects (VO). En Kotlin, implementarlos es un placer gracias a data class.
Imagina que trabajamos en un sistema de reservas de hotel. Tenemos el concepto de "Período de estancia". No es una entidad, es solo un conjunto de fechas. Un candidato perfecto para un VO.
data class StayPeriod( val checkIn: LocalDate, val checkOut: LocalDate) { init { require(checkIn.isBefore(checkOut)) { "La fecha de entrada debe ser anterior a la fecha de salida" } }
val totalNights: Int get() = ChronoUnit.DAYS.between(checkIn, checkOut).toInt()
fun overlapsWith(other: StayPeriod): Boolean { return !(this.checkOut.isBefore(other.checkIn) || this.checkIn.isAfter(other.checkOut)) }}¿Por qué es genial?
- Inmutabilidad — todos los campos son
val. Nadie cambiará accidentalmente la fecha de entrada. - Validación incorporada — el bloque
initgarantiza que el objeto solo se cree en un estado correcto. - Comportamiento — el método
overlapsWithencapsula una regla de negocio. No buscas la superposición de fechas en los servicios, sino que se lo preguntas al propio objeto.
En DDD, esto se llama modelo rico: el objeto no solo almacena datos, sino que sabe cómo realizar operaciones de negocio.
Entity: cuando la identidad importa
Ahora pasemos a las Entity. A diferencia de los VO, una entidad cambia y tiene identidad. En Kotlin, usamos una clase normal, pero con énfasis en el identificador.
Ejemplo: una reserva de hotel (Booking):
class Booking( val id: BookingId, val roomNumber: Ro