Arquitectura de Software

Domain-Driven Design: Ubiquitous Language, del vocabulario del negocio al código

Construye un lenguaje compartido que permita tomar decisiones. Exploraremos reservas, logística y educación para detectar ambigüedades, escribir ejemplos precisos y hacer evolucionar nombres, contratos y pruebas con ayuda de la IA.

Cargando tus opciones de aprendizaje…

Una persona de operaciones dice que una reserva está confirmada. El equipo de pagos entiende que ya se cobró. El desarrollador interpreta que existe una fila con confirmed = true. Las tres personas creen estar hablando de lo mismo, hasta que un cliente llega y descubre que su habitación no está disponible.

El problema empezó antes del código. Una palabra compartida escondía compromisos diferentes.

El Ubiquitous Language es un lenguaje construido y utilizado por quienes conocen el negocio y quienes desarrollan el software, dentro de un Bounded Context, el límite donde el modelo conserva un significado consistente. Debe aparecer también en el código. Referencia: Martin Fowler.

La siguiente propuesta de trabajo y sus ejemplos son recomendaciones prácticas: no existe un glosario universal que podamos descargar para cualquier empresa.

Un vocabulario útil permite anticipar consecuencias

“Confirmado” solo sirve si sabemos qué permite hacer. En nuestro hotel ficticio acordamos que una reserva confirmada garantiza alojamiento, aunque el pago pueda realizarse al llegar. En una actividad con entradas limitadas, en cambio, confirmarla podría exigir pago aceptado.

No corregimos esa diferencia imponiendo una definición empresarial para todas las aplicaciones. Precisamos el significado donde se aplica y hacemos explícita la traducción cuando dos áreas colaboran.

Para empezar, preguntamos qué ocurre antes y después de la confirmación. ¿Puede cancelarse? ¿Se libera capacidad? ¿Quién puede revertirla? ¿Qué se comunica al huésped? Cada respuesta acerca la conversación a comportamiento verificable.

Un buen nombre permite formular una expectativa. Si alguien ejecuta confirmReservation, debería entender qué compromiso acaba de asumir el sistema.

Escenario 1: una reserva temporal no es una reserva confirmada

Imaginemos una plataforma que retiene una plaza durante diez minutos mientras se completa el pago. El primer modelo usa Reservation para todo y un campo active para indicar si “sigue funcionando”.

La palabra activa mezcla preguntas diferentes: si la retención venció, si el pago se aceptó y si el participante conserva su plaza. Antes de añadir más booleanos, proponemos distinguir retención y confirmación.

Retención:
Aparta capacidad hasta un instante de vencimiento.
No garantiza la participación después de ese instante.

Confirmación:
Compromiso aceptado por el sistema después de verificar
las condiciones acordadas para la actividad.

Cancelación:
Retira una confirmación y aplica la política de devolución.

Ahora podemos discutir una situación que antes quedaba oculta: el proveedor acepta el pago a las 10:11 y la retención venció a las 10:10. ¿Confirmamos igualmente, buscamos otra plaza o iniciamos devolución? El vocabulario descubre una decisión pendiente; no la inventa.

Si negocio no ha elegido, documentamos alternativas. Generar código con IA antes de resolverlo puede convertir una suposición en una regla difícil de detectar.

Llevar un acuerdo pequeño a código

Para un ejemplo autocontenido, acordemos que la retención está vigente cuando el instante actual es estrictamente anterior al vencimiento. Usaremos milisegundos enteros; no estamos resolviendo calendarios ni horarios locales.

class SeatHold {
  constructor(readonly expiresAtMs: number) {
    if (!Number.isSafeInteger(expiresAtMs)) {
      throw new Error("Vencimiento inválido");
    }
  }

  isValidAt(nowMs: number): boolean {
    if (!Number.isSafeInteger(nowMs)) throw new Error("Instante inválido");
    return nowMs < this.expiresAtMs;
  }
}

import { strict as assert } from "node:assert";
const hold = new SeatHold(1000);
assert.equal(hold.isValidAt(999), true);
assert.equal(hold.isValidAt(1000), false);

El nombre isValidAt obliga a pasar el instante de evaluación. La prueba en el vencimiento exacto deja escrito un acuerdo que “dura diez minutos” no expresaba con suficiente precisión.

El ejemplo protege una interpretación local. Aceptar una confirmación concurrente requiere coordinación persistente, además de este método.

Escenario 2: despachar, entregar y recibir

Una tienda marca un pedido como enviado cuando genera la etiqueta. El transportista considera despachado el paquete cuando lo recibe físicamente. Atención al cliente interpreta entregado como recibido por el comprador.

Si intercambiamos status: "sent" sin aclaraciones, cada equipo puede mostrar una promesa distinta. Necesitamos identificar hechos: etiqueta generada, paquete entregado al transportista, intento de entrega y recepción confirmada.

No necesariamente crearemos un estado por cada escaneo técnico. Primero preguntamos cuáles modifican decisiones del negocio. Generar una etiqueta podría permitir imprimir documentos, pero no habilitar una reclamación por retraso de tránsito.

En la integración conservaríamos el significado externo y definiríamos su correspondencia. Un estado desconocido no debería convertirse automáticamente en “entregado” o “pendiente”. Puede requerir revisión y una representación explícita de incertidumbre.

Este escenario muestra que un diccionario de traducciones literales es insuficiente. También necesitamos conocer quién tiene autoridad para afirmar el hecho y qué evidencia lo respalda.

Escenario 3: terminar una lección y aprobar un curso

En educación, “completado” puede referirse a ver un video, entregar una actividad o aprobar una evaluación. Una pantalla que muestra un cien por ciento no explica necesariamente cuál ocurrió.

Podemos distinguir progreso de consumo, actividades entregadas y requisitos de aprobación. Un estudiante podría terminar todas las lecciones y seguir sin aprobar el curso porque falta una evaluación obligatoria.

El equipo revisaría frases como “emitir certificado al completar”. ¿Completar qué? ¿Los cursos permiten cambiar sus requisitos después de una inscripción? ¿El criterio se evalúa con la versión actual o con la acordada al inscribirse?

Las respuestas pueden llevar a conceptos como CompletionCriteria o CoursePassed, pero esos nombres deben llegar después de la conversación. No añadiríamos una política intercambiable solo porque el patrón resulte atractivo.

Cómo construir el lenguaje con el equipo

Recomiendo trabajar con tres materiales: situaciones reales, términos usados y ejemplos que contradigan una interpretación. En una reunión breve, una persona explica un recorrido y otra intenta repetirlo utilizando palabras precisas. Cuando las interpretaciones divergen, anotamos la pregunta.

Una ficha de término puede incluir significado, contexto, ejemplo válido, contraejemplo y consecuencias. También conviene señalar términos evitados: si active resultó demasiado ambiguo, dejarlo documentado evita que reaparezca en otra pantalla.

La ficha no reemplaza la conversación ni necesita aprobarse mediante un proceso pesado. Su utilidad consiste en permitir que una persona nueva tome la misma decisión ante el mismo escenario.

No exigiría revisar todos los términos antes de implementar. Empezaría por los que participan en reglas importantes, rechazos y compromisos públicos.

Cómo evolucionar nombres sin romper contratos

Descubrir un término mejor no obliga a renombrar todo en una sola entrega. Podemos cambiar primero el modelo interno y mantener una adaptación del contrato público existente. Si el significado también cambia, una simple sustitución textual sería peligrosa.

Por ejemplo, reemplazar confirmed por paid rompe a quienes entendían que confirmación no exigía cobro. Hay que revisar comportamiento, datos históricos, mensajes, pruebas y consumidores.

En una migración pequeña, buscaríamos el término antiguo con rg, clasificaríamos cada aparición y distinguiríamos nombres internos de campos persistidos o APIs. Después actualizaríamos los escenarios y ejecutaríamos sus pruebas.

La aceptación debería comprobar que las decisiones siguen siendo correctas. Que el compilador no encuentre el nombre anterior es una verificación útil, pero insuficiente.

Trabajar con IA sin fabricar acuerdos

Una IA puede ayudar a descubrir inconsistencias en documentación y código. No puede confirmar por sí sola qué compromiso comercial desea asumir la organización.

Analiza estos recorridos de reservas y sus nombres actuales.

Para cada término ambiguo:
- muestra dos interpretaciones compatibles con el texto;
- explica qué resultado observable cambiaría;
- formula una pregunta para la persona de negocio;
- propone un nombre provisional para cada alternativa.

Separa hechos confirmados de supuestos.
No elijas una política de vencimiento o devolución por nosotros.
Después de resolver las preguntas, sugiere escenarios de prueba
y localiza los nombres afectados en el código proporcionado.

Para evaluar la respuesta, buscaría evidencia concreta. Una recomendación genérica como “mejorar los nombres” no permite actuar; mostrar que una pantalla dice pagado mientras el Use Case solo reserva capacidad sí permite investigar.

Señales de que el lenguaje está ayudando

Los nombres permiten seguir un recorrido sin traducir constantemente entre negocio y código. Las pruebas distinguen estados que antes parecían equivalentes. Las dudas se convierten en preguntas concretas y los contratos dicen qué prometen.

No hace falta que todas las personas hablen como el código ni que el código reproduzca cada expresión coloquial. Buscamos precisión compartida para las decisiones que importan. Cuando el equipo entiende por qué una retención vencida no equivale a una reserva cancelada, el modelo ya está aportando valor antes de introducir otra abstracción.

Cargando conversación…

Sigue aprendiendo