Ir al contenido
  1. Posts/

Todo CLI en Rust 2. Dominio inmutable y errores tipados por capa

·2 mins
Rafael Fernandez
Autor
Rafael Fernandez
Matemáticas, programación y cosas de la vida
Todo CLI en Rust sin humo - Este artículo es parte de una serie.
Parte 2: Este artículo

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:

  • id
  • title
  • status
  • created_at
  • modified_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_at solo 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:

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.

Todo CLI en Rust sin humo - Este artículo es parte de una serie.
Parte 2: Este artículo