No necesitas Microservicios (aún), necesitas aprender a dividir tu código.

Por Juan VillarroelPublicado el 5 min de lectura

No necesitas Microservicios (aún), necesitas aprender a dividir tu código.
ArquitecturaVertical SlicesMonolitoCodigo Limpio

Respuesta rápida: Deja de organizar tu código por "carpetas de archivos" y empieza a pensar en dominios de negocio. Descubre cómo el desacoplamiento en un monolito es la clave para escalar rápido y facilitar una futura migración a microservicios.

El caos de las capas horizontales: La "Cirugía de Escopeta"

Casi todos empezamos igual: una carpeta de Controllers, otra de Services y otra de Models. En los libros de texto parece el orden perfecto, pero en la vida real, a medida que la aplicación crece, este diseño se convierte en una trampa.

Seguro te ha pasado: te piden cambiar un campo en la factura (Invoices). Para lograrlo, tienes que abrir 5 pestañas en tu editor: el controlador de facturas, el servicio de facturas, el repositorio, el DTO y la validación. Todas en carpetas distintas, perdidas entre otros 50 archivos que no tienen nada que ver con lo que estás haciendo.

A esto se le llama Shotgun Surgery (cirugía de escopeta): un solo cambio te obliga a disparar modificaciones por todo el proyecto. El acoplamiento es total y la carga cognitiva (el esfuerzo mental para entender dónde está cada cosa) es altísima.

Vertical Slices: Pensar en Dominios, no en Archivos

La propuesta es simple pero transformadora: Divide tu aplicación por lo que hace, no por lo que es. En lugar de agrupar todos los controladores del mundo en un solo lugar, agrupamos todo lo relacionado a un contexto de negocio o "slice vertical". Como explica Jimmy Bogard, cada caso de uso reúne sus preocupaciones de punta a punta. Imagina que cada funcionalidad es un pastel: en lugar de comerte solo la capa de arriba, cortas una rebanada vertical que incluye cada capa (UI, lógica y datos).

Estructura sugerida:

  • Auth: (Controller, Service, JWT logic, Repository)
  • Users: (Controller, Profile Service, Validations)
  • Invoices: (Controller, Tax Logic, PDF Generator, Repo)

Al organizar tu monolito así, estás creando contextos desacoplados. Si el día de mañana el módulo de Invoices crece tanto que necesita su propia base de datos, la migración a un microservicio será casi un "copy-paste".

Ejemplo real: el slice de Invoices

Para que no se quede solo en teoría, así se vería una petición completa dentro de un mismo slice: entra por el controlador, se valida con un DTO, pasa por la lógica de negocio en el servicio y termina persistida por el repositorio. Todo el flujo vive junto, en la misma carpeta invoices/:

// invoices/dto/create-invoice.dto.ts
export class CreateInvoiceDto {
  @IsUUID()
  customerId: string;

  @IsNumber()
  @Min(0)
  amount: number;

  @IsISO8601()
  dueDate: string;
}

// invoices/invoices.controller.ts
@Controller("invoices")
export class InvoicesController {
  constructor(private readonly invoicesService: InvoicesService) {}

  @Post()
  create(@Body() dto: CreateInvoiceDto) {
    return this.invoicesService.create(dto);
  }
}

// invoices/invoices.service.ts
@Injectable()
export class InvoicesService {
  constructor(private readonly invoicesRepository: InvoicesRepository) {}

  async create(dto: CreateInvoiceDto): Promise<Invoice> {
    const tax = dto.amount * TAX_RATE;
    return this.invoicesRepository.save({ ...dto, tax });
  }
}

// invoices/invoices.repository.ts
@Injectable()
export class InvoicesRepository {
  constructor(private readonly db: DatabaseService) {}

  save(invoice: Omit<Invoice, "id">): Promise<Invoice> {
    return this.db.invoices.create({ data: invoice });
  }
}

Cuatro archivos, un solo propósito. Nadie necesita saber cómo funciona Auth o Users para entender o modificar esto: todo el contexto de negocio de "facturas" está a un Cmd+P de distancia.


¿Por qué este enfoque cambia las reglas del juego?

Dividir por slices verticales no es solo una preferencia estética, es una decisión estratégica que trae beneficios tangibles:

  1. Onboarding ultra rápido: Cuando un desarrollador nuevo llega al equipo y tiene que tocar "Pagos", no tiene que aprenderse toda la arquitectura del sistema. Solo necesita abrir la carpeta Payments y ahí tendrá todo el contexto.
  2. Facilidad de Testing: Los tests unitarios y de integración viven al lado del código que prueban. Es mucho más sencillo mockear dependencias cuando las fronteras del dominio están claras.
  3. Velocidad de entrega (Time-to-Market): Al reducir la fricción de navegar por carpetas infinitas, el equipo desarrolla funcionalidades de punta a punta de forma más ágil.
  4. Menos conflictos en Git: Es menos probable que dos desarrolladores toquen los mismos archivos si uno está trabajando en Auth y el otro en Invoices. Cada uno trabaja en su "rebanada".

Pros y Contras: La honestidad ante todo

Como toda decisión de arquitectura, esto no es una bala de plata. Hay intercambios (trade-offs) que debes conocer.

Ventajas (Pros)

  • Bajo Acoplamiento: Un cambio en la lógica fiscal dentro de Invoices tiene nulas probabilidades de romper el login en Auth.
  • Navegación Intuitiva: El proyecto se explica solo a través de sus carpetas de negocio.
  • Escalabilidad Evolutiva: Te permite crecer de forma organizada. Si el monolito se vuelve gigante, ya está "pre-segmentado" para convertirse en microservicios sin dolor.

¿Cuándo NO es beneficioso? (Contras)

  • Aplicaciones tipo CRUD básico: Si tu app solo hace 4 operaciones básicas sobre 2 tablas, crear esta estructura es Overengineering. Añades carpetas y archivos innecesarios para algo que no va a escalar.
  • Duplicación de código intencional: A veces, para mantener los slices independientes, podrías terminar duplicando una clase de UserDTO en dos módulos. Para algunos desarrolladores puristas, esto es difícil de aceptar (aunque a veces es mejor que una dependencia acoplada).
  • Disciplina del equipo: Requiere que todos entiendan dónde termina un dominio y empieza otro. Si el equipo no tiene experiencia, pueden terminar creando "Slices monstruos" que mezclan todo de nuevo.

Conclusión

Organizar tu código por dominios desde el día 1 no es "escalado prematuro". Es higiene técnica y respeto por tu "yo" del futuro. Un monolito bien modularizado es mucho más valioso, barato de mantener y fácil de evolucionar que un ecosistema de microservicios mal gestionados.

No pienses en archivos, piensa en capacidades de negocio. Tu código (y tu salud mental) te lo agradecerán.

Cómo llevarlo a un proyecto real

No reorganizaría todo el repositorio en una sola rama. Empezaría por una capacidad con cambios frecuentes y límites reconocibles, por ejemplo facturación. Primero escribiría una prueba del comportamiento crítico; después movería controller, operación, validación y persistencia hacia el mismo módulo sin cambiar la API pública.

Un orden de migración seguro es:

  1. Elegir un caso de uso y enumerar sus dependencias reales.
  2. Crear el nuevo slice y mover únicamente ese flujo.
  3. Mantener un adaptador temporal hacia código compartido antiguo.
  4. Ejecutar pruebas y observar errores o latencia en producción.
  5. Eliminar el adaptador cuando ningún flujo dependa de él.

Durante la migración, registra imports entre módulos. Si Invoices necesita leer datos de Users, expón una operación concreta en lugar de importar su repositorio. La dirección de esa dependencia dice más sobre la arquitectura que el nombre de las carpetas.

También aceptaría duplicación pequeña y temporal. Extraer un helper compartido demasiado pronto puede recrear el acoplamiento que intentabas eliminar. Cuando dos slices cambien juntos de forma repetida, entonces existe evidencia para diseñar una abstracción común.

Si trabajas con TypeScript, mi experiencia con NestJS ayuda a evaluar cómo sus módulos y providers pueden representar estos límites sin convertir cada operación en ceremonia.

La regla sigue siendo la misma: crea el límite porque protege un cambio frecuente, no porque un diagrama se vea más profesional. Esa disciplina conecta directamente con tomar decisiones imperfectas pero correctas.

Publicaciones Relacionadas