Primitive Obsession: ¿Sigues pasando strings y numbers crudos por toda tu aplicación?

Imagina que estás dirigiendo la escena más importante de tu proyecto. Tienes la arquitectura perfecta, la infraestructura ideal y las bases de datos listas. Dices «¡Acción!»… y de repente, te das cuenta de que en lugar de asegurar un dato robusto para tu regla de negocio, metiste un tipo de dato genérico que pasaba por ahí.

La ejecución falla o, peor aún, corrompe la base de datos.

En el desarrollo de software, esto se llama Primitive Obsession (Obsesión por los primitivos). Ocurre cuando usamos tipos de datos genéricos como string o number para representar conceptos vitales de nuestro negocio, como un «Correo Electrónico» o un «Identificador de Usuario».

Un string acepta cualquier texto. No tiene reglas. Es un extra sin preparación. Hoy vamos a hacer un «casting» riguroso utilizando @nestjslatam/ddd-lib para asegurarnos de que solo los actores correctos entren a nuestro dominio.

El problema de los extras (Primitivos)

Mira este código. Parece inofensivo, pero es una bomba de tiempo en producción:

// 🚩 Código frágil: Dependencia de primitivos
function createUser(id: string, email: string) {
  // ¿Qué pasa si 'id' viene vacío?
  // ¿Qué pasa si 'email' es la palabra "hola"?
  // El compilador dice que todo está bien, pero el negocio explota.
}

Aquí, TypeScript nos protege a nivel técnico, pero es ciego a nivel de negocio. Necesitamos encapsular estas reglas.

¡Acción! Instalando nuestra librería

Primero, preparamos el entorno instalando nuestra herramienta principal:

npm install @nestjslatam/ddd-lib

Escena 1: Creando el Value Object para el Email

En lugar de un string crudo, vamos a crear un especialista: la clase UserEmail. Utilizaremos la clase base StringValueObject que nos proporciona la librería. Esta clase base ya se encarga de la inmutabilidad y de comparar valores por debajo del capó.

import { StringValueObject } from '@nestjslatam/ddd-lib';

export class UserEmail extends StringValueObject {
  private constructor(value: string) {
    super(value);
  }

  // El "Director de Casting": Solo deja pasar correos válidos
  public static create(value: string): UserEmail {
    this.ensureIsValidEmail(value);
    return new UserEmail(value);
  }

  private static ensureIsValidEmail(value: string): void {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(value)) {
      throw new Error(`El correo no tiene un formato válido. ¡Corte!`);
    }
  }
}

¿Qué acabamos de lograr aquí?

  • Control total: Nadie puede instanciar new UserEmail() directamente gracias al constructor privado.
  • Falla rápido: Si el correo es inválido, la ejecución se corta inmediatamente en el método estático create(). La data corrupta nunca llega a tu base de datos.
  • Significado: Ahora tu código habla el lenguaje de tu negocio, no el de la máquina.

Escena 2: El Identificador Único (UUID)

Todo actor principal necesita un identificador único. En nuestras bases de datos, eso suele ser un UUID. Nuestra librería nos facilita enormemente este trabajo, ahorrándonos la necesidad de reinventar la rueda para cada entidad.

import { UuidValueObject } from '@nestjslatam/ddd-lib';

export class UserId extends UuidValueObject {
  // La clase base UuidValueObject ya valida que el string sea un UUID v4 real.
  // Solo necesitamos crear un método factoría para mayor claridad.
  
  public static create(id: string): UserId {
    return new UserId(id);
  }

  // También podemos generar uno nuevo automáticamente para usuarios recién registrados
  public static random(): UserId {
    return new UserId(UuidValueObject.random().value);
  }
}

El Corte Final: El Dominio en Producción

Ahora, volvamos a nuestra función inicial y veamos cómo ha cambiado el panorama. Nuestro código ahora es robusto, expresivo y a prueba de balas:

// 🎬 Código de calidad cinematográfica
function createUser(id: UserId, email: UserEmail) {
  // Si llegamos a esta línea, TENEMOS LA GARANTÍA ABSOLUTA
  // de que 'id' es un UUID real y 'email' es un correo válido.
  // No hay necesidad de hacer "if/else" aquí adentro.
  
  console.log(`Grabando usuario: ${id.value} con contacto ${email.value}`);
}

// Así se ejecuta en la vida real:
const myId = UserId.random();
const myEmail = UserEmail.create('contacto@nestjslatam.dev');

createUser(myId, myEmail);

¡It’s a wrap!

Hemos despedido a los extras (strings sueltos) y contratado a especialistas (UserEmail, UserId). Al usar @nestjslatam/ddd-lib, hemos centralizado las reglas de negocio en pequeños objetos inmutables.

Si mañana cambia la regla de validación de los correos, solo tienes que modificar un solo archivo en toda tu aplicación. Eso es arquitectura de alto nivel.

En el próximo artículo, subiremos la apuesta y veremos cómo lidiar con gestores de validación: para entender cómo manejar múltiples reglas de negocio y validaciones avanzadas sin ensuciar nuestros Value Objects.

¿Ya estás eliminando la obsesión por los primitivos en tus proyectos con NestJS? Cuéntame tu experiencia en los comentarios.

Leave a Comment