Introducción a los Modelos de Dominio
Abstract
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.
Idea central
¿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.
Caso de estudio: fechas y horas
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 Problemas
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 Observación
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) Mejoras
Importante
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).
Tipos primero
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")
} Problemas
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")
} Mejoras
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.
Regla práctica
Expresividad
Los nombres deben describir su propósito sin necesidad de comentarios adicionales. Un buen nombre actúa como documentación implícita
Tip
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]
}
} Problemas
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]
}
} Mejoras
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.
Tip
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.
Tip
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 classdata class,sealed classsealed classy más. Incluso se proyecta incluir union types para modelar errores de manera más precisa.value classvalue 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.
Tip
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.
en lugar deInstantInstant) mejora la expresividad y la seguridad.floatfloat - 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
- Guí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.