fundamentos · temario
1.2tema 2 de 5

Lenguaje Ubicuo

Cuando el código habla el idioma del negocio, los errores se vuelven visibles.

El lenguaje ubicuo (Ubiquitous Language) es el vocabulario compartido y preciso que usan por igual el equipo técnico y los expertos del negocio. No es un glosario externo ni un documento de requisitos: es el lenguaje que aparece literalmente en el código, en los tests, en las conversaciones de equipo y en los tickets. Cuando un experto del dominio lee un nombre de clase o método y lo reconoce de inmediato, el lenguaje ubicuo está funcionando.

La razón por la que el lenguaje importa tanto es que la traducción constante genera errores. Cuando un desarrollador convierte mentalmente 'solicitud de aprobación' (término del negocio) a 'ApprovalRequest' (clase) y luego a 'approval_requests' (tabla), y el equipo de negocio habla de 'trámite de visación', hay tres términos para el mismo concepto. Cada salto de traducción introduce una oportunidad para que el modelo capture mal la realidad del dominio.

El lenguaje ubicuo es específico de un Bounded Context. El mismo término puede tener significados distintos en distintos contextos del negocio —y eso está bien— siempre que dentro de cada contexto el término sea unívoco y preciso. Pretender un glosario global único para toda la organización es una trampa: fuerza compromisos que degradan la precisión del modelo en cada contexto.

Construir el lenguaje ubicuo es un proceso iterativo. Se descubre hablando con expertos del dominio, cuestionando términos ambiguos, y refactorizando el código cuando el vocabulario del negocio evoluciona. Si un experto introduce un término nuevo que el equipo no ha escuchado antes, o si el equipo usa un nombre técnico que el experto no reconoce, es una señal de que el modelo necesita revisión.

En Python, el lenguaje ubicuo se expresa en los nombres de clases, métodos, variables y módulos. Un método llamado `update_record()` es vocabulario técnico. El mismo método renombrado `submit_request()` o `approve_claim()` comunica intención de negocio. La diferencia parece cosmética pero tiene consecuencias profundas: el primer nombre obliga a leer el cuerpo del método para entender qué hace; el segundo lo dice solo.

El glosario vivo no es un documento Word que se escribe una vez y se archiva. Es una artefacto del equipo que evoluciona con el dominio. Cada vez que el equipo aclara un término ambiguo, el código debe reflejar esa claridad. El refactoring de nombres es tan parte del trabajo de DDD como el refactoring de lógica.

structure.txt
# domain/model/<aggregate>.py
# Names come from the domain expert's vocabulary, not from DB schema.

from dataclasses import dataclass
from .<value_object> import <StatusValue>, <AmountValue>
from ..events.<something_happened> import <RequestSubmitted>, <RequestApproved>

@dataclass
class <Request>:           # NOT: Record, Item, Entity, Row
    id: <RequestId>
    _status: <StatusValue>  # NOT: s, flag, state_code
    _amount: <AmountValue>  # NOT: val, num, data

    def submit(self) -> None:
        # Business verb: 'submit', not 'save', 'persist', 'insert'
        if self._status != <StatusValue>.DRAFT:
            raise <AlreadySubmittedError>(self.id)
        self._status = <StatusValue>.SUBMITTED
        self._events.append(<RequestSubmitted>(request_id=self.id))

    def approve(self, reason: <ApprovalReason>) -> None:
        # Business verb: 'approve', not 'update_status'
        if self._status != <StatusValue>.SUBMITTED:
            raise <InvalidTransitionError>(self._status, 'approve')
        self._status = <StatusValue>.APPROVED
        self._events.append(<RequestApproved>(request_id=self.id, reason=reason))

# domain/model/<value_object>.py
# VO names are noun phrases from the domain glossary.
@dataclass(frozen=True)
class <AmountValue>:
    # NOT: amount_val, price_data, num
    value: int          # e.g. cents — unit documented in the class
    currency: str       # ISO 4217

    def __post_init__(self) -> None:
        if self.value < 0:
            raise <NegativeAmountError>(self.value)

Debugging lab

Detecta y corrige el error o la violación de diseño.

0/5 tests passing0%
  1. 1.2.5.1

    class RecordManager: def process(self, data: dict) -> None: if data.get('status') == 1: data['status'] = 2 self.repo.save(data)

  2. 1.2.5.2

    # Domain Event @dataclass(frozen=True) class StatusUpdate: entity_id: str new_status: str timestamp: datetime

  3. 1.2.5.3

    class Utils: @staticmethod def check(val: float) -> bool: return 0 < val <= 1_000_000

  4. 1.2.5.4

    def handle_event(event_type: str, payload: dict) -> None: if event_type == 'approval': # ... process approval pass

  5. 1.2.5.5

    # Two different contexts use the same class from contexts.billing.domain.model.customer import Customer from contexts.support.domain.model.customer import Customer # conflict