Introducción a los Modelos de Dominio
Esta lección introduce el modelado de dominio como la práctica de representar en el código los conceptos, reglas y relaciones reales del problema que buscamos resolver. El objetivo no es solo que el software funcione, sino que exprese con claridad el lenguaje del dominio, reduciendo ambigüedades y errores.
A través de ejemplos contrastantes (como el manejo de fechas en Python y Kotlin) exploramos cómo los tipos ricos y los nombres expresivos hacen que el código sea más seguro, predecible y fácil de mantener. Finalmente, destacamos cómo Kotlin entrega herramientas —como null safety, data classes, sealed classes, value classes, funciones de extensión y DSLs— que potencian el modelado de dominios y permiten construir bibliotecas claras, consistentes y sostenibles.
¿Qué es un Modelo de Dominio?
Un modelo de dominio es la representación explícita de los conceptos, reglas y relaciones que definen el problema que un sistema busca resolver. Su objetivo no es describir la infraestructura técnica, sino capturar el lenguaje del dominio y expresarlo de forma clara en el código.
Modelar el dominio implica identificar entidades, valores y comportamientos relevantes, y organizarlos con tipos y estructuras que comuniquen su propósito sin ambigüedad. De esta manera, el software refleja el mundo real que intenta representar.
¿Por qué es importante modelar el dominio?
Modelar el dominio hace que el código exprese el problema con precisión. Cuando usamos tipos y nombres de dominio, el código se vuelve más claro, difícil de usar mal y más fácil de evolucionar. Para verlo, comparemos cómo representamos una fecha/hora.
time
, basado en primitivas), (B) Python con anotaciones de
tipos pero aún con primitivas, y (C) Kotlin con un modelo de dominio
explícito usando
kotlinx-datetime
.
now
now
es un float
float
cualquiera
y local_date_time
local_date_time
es solo texto. Ambigüedad
de unidades/zonas y pocas garantías.
now = time.time() # segundos desde epoch
local_date_time = time.ctime(now) # representación textual
float
float
, formato libre en str
str
, errores tardíos.
now: float = time.time() # segundos desde epoch
local_date_time: str = time.ctime(now) # representación textual
Instant
Instant
y LocalDateTime
LocalDateTime
comunican semántica y habilitan operaciones seguras (zona horaria, conversión,
validación).
val now: Instant = Clock.System.now()
val localDateTime: LocalDateTime = now.toLocalDateTime(TimeZone.UTC)
time
time
, que expone primitivas. Python también
incluye
datetime
datetime
, que ofrece un modelo de dominio
más expresivo y cercano a la idea de Instant
Instant
y
LocalDateTime
LocalDateTime
en Kotlin.
Beneficios de Modelar el Dominio
- Claridad — nombres y tipos cuentan la historia del dominio.
- Seguridad — es más difícil representar estados inválidos.
- Evolución — cambios guiados por el lenguaje ubicuo del equipo.
- Descubribilidad — la API sugiere operaciones válidas (autocompletado).
Int
Int
, String
String
o float
float
)
reduce ambigüedades y errores. Cada tipo captura un concepto específico
del dominio, haciendo el código más expresivo y seguro.
La importancia de los nombres en el modelado de dominios
En el modelado de dominios, los nombres son fundamentales. Un buen nombre expresa con claridad la intención detrás de una función, clase o variable. No se trata solo de estilo: la consistencia y expresividad de los nombres determinan la facilidad de comprensión, mantenimiento y colaboración en el código.
Consistencia
La consistencia en nombres, estructuras y convenciones es clave para que un modelo de dominio sea fácil de leer y mantener. Si cada parte del código usa un estilo distinto, la persona que lo lee debe hacer un esfuerzo adicional para entender si las diferencias son intencionales o accidentales. En cambio, cuando seguimos patrones claros y uniformes, el código se vuelve más predecible y menos propenso a errores.
class PackageManager {
fun addPackage(p: Package): Unit =
TODO("Register a package in the system")
fun uninstallDependency(dep: Package): Unit =
TODO("Remove a package from the system")
fun batchInstallPkgs(pkgs: List<Package>): Unit =
TODO("Install multiple packages at once")
fun removeBatch(pkgs: List<Package>): Unit =
TODO("Uninstall multiple packages at once")
}
add
add
/ remove
remove
/ uninstall
uninstall
/ install
install
), sustantivos cambiantes ( Package
Package
/ Dependency
Dependency
/ Pkgs
Pkgs
),
pluralización irregular, y prefijos ( batch
batch
)
usados de forma asimétrica.
class PackageManager {
fun installPackage(pkg: Package): Unit =
TODO("Register a package in the system")
fun uninstallPackage(pkg: Package): Unit =
TODO("Remove a package from the system")
fun batchInstallPackages(packages: List<Package>): Unit =
TODO("Install multiple packages at once")
fun batchUninstallPackages(packages: List<Package>): Unit =
TODO("Uninstall multiple packages at once")
}
Usa verbos uniformes: si existen varias formas de nombrar algo,
elige una y sé consistente. Evita mezclar
con
install
install
, o remove
remove
con
add
add
. Quienes usan la API deben poder
anticipar el verbo complementario de forma natural: si existe
uninstall
uninstall
, esperarán install
install
, no uninstall
uninstall
. Y si conviven remove
remove
y install
install
, surge la duda: ¿son sinónimos? ¿por
qué hay dos formas de expresar lo mismo? ¿o significan cosas
distintas? Estas ambigüedades generan fricción y confusión que
podemos evitar con consistencia.
add
add
Cuando los términos del dominio sean parecidos o ambiguos,
conviene documentar y estandarizar las convenciones del equipo.
Una buena referencia es la guía de verbos aprobados de
PowerShell , que diferencia, por ejemplo, entre
(obtener acceso a un recurso) y
Get
Get
(abrir un recurso y extraer su contenido).
Así, Read
Read
devuelve un handle mientras
que
Get-File
Get-File
lee el archivo.
Read-File
Read-File
Prefiere además sustantivos claros, plurales regulares y evita abreviaciones crípticas; si usas prefijos o sufijos, hazlo siempre de forma simétrica.
Expresividad
Los nombres deben describir su propósito sin necesidad de comentarios adicionales. Un buen nombre actúa como documentación implícita
a1
, a2
,
..., aN
, ya que no aportan información sobre el propósito o
contenido de las variables.
fun copyChars(a1: CharArray, a2: CharArray) {
for (i in a1.indices) {
a2[i] = a1[i]
}
}
a1
a1
y
a2
a2
no aportan contexto ni ayudan a entender la
función.
fun copyChars(source: CharArray, destination: CharArray) {
for (i in source.indices) {
destination[i] = source[i]
}
}
source
source
y
destination
destination
dejan claro el propósito de cada
argumento.
Claridad sobre brevedad
Ahorrar caracteres en los nombres puede parecer práctico en el corto
plazo, pero suele ser un error costoso: cada abreviación reduce la claridad y aumenta la carga cognitiva.
Es preferible un nombre largo pero claro que uno corto y ambiguo.
fun cx(cxPb: Prob): Gt {
// ...
}
fun performCrossover(crossoverProbability: Probability): Genotype {
// ...
}
Pronunciabilidad
Como dice Robert C. Martin en Clean Code:
“Si no puedes pronunciarlo, no puedes hablar de ello sin sonar como un tonto. (…) Esto importa porque la programación es una actividad social.”
Los nombres difíciles de pronunciar o recordar generan fricción en las conversaciones técnicas y ralentizan el trabajo en equipo. Un buen nombre debería poder leerse en voz alta de manera natural y sin ambigüedades.
strtok
strtok
o memcpy
memcpy
, nacieron de
limitaciones técnicas, pero hoy resultan poco expresivos.
char *strtok(char *str, const char *delim); // "tokenize string"
void *memcpy(void *dest, const void *src, size_t n); // "copy memory"
fun split(input: String, delimiter: String): List<String> { /* ... */ }
fun copyMemory(destination: ByteArray, source: ByteArray, length: Int) { /* ... */ }
¿Cómo aporta Kotlin al modelado de dominios?
Kotlin combina la programación orientada a objetos y la programación funcional (aunque aún no de manera tan completa como Scala o Haskell), lo que le permite ofrecer un conjunto muy completo de herramientas para construir modelos de dominio expresivos y seguros. El objetivo no es aprender todos estos conceptos de inmediato, sino reconocer que el lenguaje pone a disposición mecanismos potentes que iremos explorando a lo largo del curso.
- Tipos ricos: el sistema de tipos de Kotlin permite expresar mejor la semántica del dominio mediante
data class
data class
sealed class
sealed class
value class
value class
- Abstracciones flexibles: interfaces, clases abstractas, typeclasses a través de context parameters y jerarquías bien estructuradas.
- Funciones de extensión: permiten añadir comportamientos a tipos ya existentes sin romper la encapsulación, enriqueciendo el dominio de forma local y clara.
- Null safety: la separación explícita entre tipos anulables y no anulables reduce errores comunes y hace que la semántica sea más explícita.
- DSLs (Domain-Specific Languages): la sintaxis flexible de Kotlin facilita construir APIs internas que reflejen directamente el lenguaje del dominio.
- Expresividad creciente: el ecosistema evoluciona constantemente y cada nueva característica busca dar más precisión y seguridad al modelado.
Conclusiones
A lo largo de esta lección vimos que modelar el dominio no es un lujo, sino una necesidad para que el software refleje con claridad los conceptos y reglas del problema que busca resolver. El uso de tipos ricos, nombres consistentes y expresivos, y las herramientas que ofrece Kotlin nos permite reducir ambigüedades, evitar errores y construir sistemas que evolucionen de manera sostenible.
Un buen modelo de dominio conecta el lenguaje del equipo con el diseño del código: cuanto más clara sea esa conexión, más fácil será comunicarse, colaborar y extender el sistema en el futuro.
Puntos clave
- Tipos de dominio: reemplazar primitivas genéricas por tipos específicos (p. ej.
Instant
Instant
float
float
- Nombres claros y consistentes: evitan ambigüedades y actúan como documentación implícita.
- Expresividad sobre brevedad: abreviaciones crípticas reducen la claridad; los nombres completos facilitan el mantenimiento y la colaboración.
- Pronunciabilidad: los nombres deben poder usarse de forma natural en conversaciones técnicas.
- Kotlin como aliado: con null safety, data classes, sealed classes, value classes, funciones de extensión y DSLs, el lenguaje entrega múltiples mecanismos para construir modelos de dominio expresivos.
¿Qué nos llevamos?
El modelado de dominios no se trata solo de escribir código correcto, sino de escribir código que cuente una historia clara del problema. Kotlin nos da las herramientas para hacerlo: depende de nosotrxs decidir usarlas con consistencia, claridad y expresividad.
Al final, un buen modelo de dominio es una inversión: reduce errores, facilita la comunicación y prolonga la vida útil de las bibliotecas que construimos.
¿Con ganas de más?
Referencias recomendadas
- “Meaningful Names” en Clean Code: A Handbook of Agile Software Craftsmanship por Robert C. MartinGuía práctica para nombrar bien en código: cómo elegir nombres que revelen intención, eviten desinformar, hagan distinciones significativas, sean pronunciables y buscables, y no dependan de codificaciones (HN, prefijos). Muestra anti-ejemplos (“a1”, “d”, “getThem”) y su refactor a nombres claros (“elapsedTimeInDays”, “getFlaggedCells”, source/destination). Insiste en consistencia léxica (un verbo por concepto), evitar juegos de palabras y añadir contexto mediante tipos/abstracciones en lugar de sufijos vacíos (“Info”, “Data”). Complementa la lección al dar criterios concretos para alinear nombres con el modelo de dominio, reduciendo ambigüedad y errores y haciendo el código más legible y mantenible.
- “Communication and the Use of Language” (pp. 24–36) en Domain-Driven Design: Tackling Complexity in the Heart of Software por Eric EvansExpone la importancia de un lenguaje ubicuo en proyectos de software: un vocabulario compartido entre desarrolladores y expertxs de dominio, basado en el modelo del dominio, que se use en el código, en las conversaciones, en la documentación y en los diagramas. Señala los riesgos de depender de traducciones entre jergas distintas (malentendidos, refactorizaciones innecesarias, software inconsistente) y muestra cómo el compromiso con un lenguaje común impulsa el refinamiento del modelo y una comunicación más fluida. Además, reflexiona sobre el rol de diagramas, documentos y modelos explicativos como apoyos a la comunicación, siempre subordinados al lenguaje compartido y al código.