DDD en TypeScript: Arquitectura limpia para proyectos complejos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Por qué el CRUD común mata tu proyecto y cómo DDD puede solucionarlo

\n\n

Cada desarrollador se ha enfrentado al menos una vez a la situación en la que un proyecto aparentemente simple se convierte en un "pantano" de servicios, controladores y lógica de negocio dispersa por todo el código. Empiezas escribiendo UserService, luego OrderService, y seis meses después en OrderService hay lógica para enviar correos electrónicos, calcular descuentos y verificar el saldo del usuario. ¿Te suena familiar?

\n\n

El problema es que pensamos demasiado pronto en bases de datos, frameworks y APIs REST. Nos olvidamos de lo principal: el dominio (domain). Domain-Driven Design (DDD) propone un enfoque radicalmente diferente: primero modelamos los procesos de negocio en el lenguaje de los expertos, y solo después el código.

\n\n

En este artículo, analizaremos cómo aplicar Domain-Driven Design en TypeScript con ejemplos reales. Verás cómo construir una arquitectura limpia donde las reglas de negocio viven separadas de la infraestructura y el código es fácil de probar y modificar.

\n\n

Fundamentos de DDD: lenguaje, contextos y modelo

\n\n

Ubiquitous Language (Lenguaje ubicuo)

\n

El primer principio de DDD es un lenguaje común, comprensible tanto para desarrolladores como para expertos de negocio. Si el gerente dice "El cliente activó la suscripción", en el código debe haber un método activateSubscription(), no setStatus(1). TypeScript con su tipado estricto es ideal para esto: los nombres de clases, métodos y variables se convierten en documentación.

\n\n

Bounded Context (Contexto delimitado)

\n

Un sistema grande se divide en contextos independientes: "Pagos", "Gestión de usuarios", "Envíos". Cada contexto tiene su propio modelo y lenguaje. Los módulos de TypeScript y los tipos estrictos ayudan a separar rígidamente estos contextos a nivel de importaciones.

\n\n

Construyendo el modelo de dominio en TypeScript

\n\n

Value Object (Objeto de valor)

\n

Es una entidad inmutable que se define por sus atributos, no por un identificador. Ejemplo: Email, Money, Coordinates.

\n\n
// value-objects/Email.ts\nexport class Email {\n  private readonly value: string;\n\n  constructor(email: string) {\n    if (!this.isValid(email)) {\n      throw new Error('Invalid email address');\n    }\n    this.value = email;\n  }\n\n  private isValid(email: string): boolean {\n    return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n  }\n\n  public getValue(): string {\n    return this.value;\n  }\n\n  public equals(other: Email): boolean {\n    return this.value === other.value;\n  }\n}\n
\n\n

Observa: Email valida su propia validez. Ningún servicio debe saber cómo se ve un email correcto. Esto es Domain-Driven Design en TypeScript puro: la regla de negocio vive dentro del objeto.

\n\n

Entity (Entidad)

\n

A diferencia de Value Object, una entidad tiene un identificador (ID) y puede cambiar con el tiempo. Ejemplo: User, Order.

\n\n
// entities/User.ts\nimport { Email } from '../value-objects/Email';\nimport { UserId } from '../value-objects/UserId';\n\nexport class User {\n  private readonly id: UserId;\n  private email: Email;\n  private isActive: boolean;\n\n  constructor(id: UserId, email: Email) {\n    this.id = id;\n    this.email = email;\n    this.isActive = true;\n  }\n\n  public getId(): UserId {\n    return this.id;\n  }\n\n  public changeEmail(newEmail: Email): void {\n    if (!this.isActive) {\n      throw new Error('Cannot change email for inactive user');\n    }\n    this.email = newEmail;\n  }\n\n  public deactivate(): void {\n    this.isActive = false;\n  }\n}\n
\n\n

Aggregate (Agregado)

\n

Un agregado es un grupo de entidades y objetos de valor que se manejan como una unidad. El agregado tiene una raíz (Aggregate Root) a través de la cual se accede a los objetos internos.

\n\n

Ejemplo: un pedido (Order)

Blogs

Book Recommendations