Seguimos con la serie.
Read this chapter in English: EN
Con la arquitectura ya definida, tocaba la parte más importante: modelar el dominio para que las reglas no dependan de acordarse de ellas.
Código de referencia:
Entidad Task: diseño orientado a invariantes #
Task tiene:
idtitlestatuscreated_atmodified_at
Hasta aquí nada nuevo. Lo relevante es la semántica de cambio.
Transiciones inmutables (self -> Result<Self>)
#
Elegimos que los cambios sean así:
pub fn mark_done(self) -> DomainResult<Self>
pub fn mark_todo(self) -> DomainResult<Self>
pub fn edit_title(self, title: String) -> DomainResult<Self>Por qué esto en vez de &mut self
#
Con &mut self, suele aparecer esta deriva:
- un caso de uso cambia estado,
- otro caso de uso toca timestamps,
- otro caso de uso valida “a su manera”.
Resultado: reglas repartidas y comportamiento inconsistente.
Con transiciones inmutables en dominio, toda modificación pasa por una puerta única con validación incluida.
Ejemplo concreto de regla compuesta #
En mark_as pasa esto en un mismo punto:
- evita transición al mismo estado (
Todo -> Todo,Done -> Done), - preserva
created_at, - actualiza
modified_atsolo cuando sí cambia el estado.
Ese detalle evita bugs de auditoría donde el timestamp cambia sin que haya cambiado nada.
Taxonomía de errores por capa #
Errores por responsabilidad:
- Dominio: DomainError
- Repositorio: RepoError
- Aplicación: ApplicationError
- CLI: CliError
Por qué no usar un error genérico global #
Si todo termina en “Error: algo falló”, pierdes contexto técnico y contexto funcional.
Caso real:
done <id>con id inexistente no es un fallo de infraestructura.- Es una violación de regla de negocio (
TaskNotFound).
Modelarlo así mejora mensajes y facilita testeo por tipo de error.
Use cases como orquestadores, no como dueños de reglas #
Un caso de uso típico quedó como find -> transition -> save.
Por ejemplo:
El caso de uso no decide si la transición es válida; solo coordina repositorio + dominio.
Señal de madurez del diseño #
Cuando añadimos nuevos detalles de salida y persistencia, las reglas de estado no se tocaron. Eso indica que el dominio no está contaminado por preocupaciones externas.
Cierre #
Este punto marca la diferencia entre una app que “hace cosas” y una app que garantiza comportamiento.
En el siguiente post vamos con la capa visible: CLI, UX y salida para humanos y automatización.