Conceptos clave de arquitectura

Acoplamiento, cohesión, separación de responsabilidades, principios SOLID aplicados a arquitectura y bounded contexts.

Construir un lenguaje común

Antes de profundizar en arquitectura distribuida, conviene fijar un vocabulario común. Estos términos aparecerán una y otra vez a lo largo de toda la plataforma.

Muchas decisiones técnicas parecen complejas solo porque faltan definiciones compartidas. En esta sección construiremos ese lenguaje común y los principios que guían las decisiones arquitectónicas.

Dominio

Es el área del negocio que el software intenta resolver. Por ejemplo: usuarios, productos, órdenes, pagos, inventario.

El dominio no es un concepto técnico sino de negocio. Entender el dominio es el primer paso para diseñar una buena arquitectura.

Subdominio

Una parte específica dentro del dominio general del negocio. Ayuda a descomponer el problema en áreas más manejables.

Bounded Context

Un límite explícito dentro del cual ciertos términos, modelos y reglas tienen un significado concreto.

¿Por qué importan?

En un sistema grande, la misma palabra puede significar cosas diferentes según el contexto:

  • “Usuario” en autenticación: email, contraseña, roles.
  • “Usuario” en facturación: nombre fiscal, dirección, método de pago.
  • “Usuario” en soporte: historial de tickets, nivel de satisfacción.

Cada bounded context tiene su propio modelo con los atributos que necesita.

Mapeo a arquitectura

┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│   Autenticación  │  │   Facturación    │  │     Soporte      │
│                  │  │                  │  │                  │
│  Usuario:        │  │  Cliente:        │  │  ContactoSoporte:│
│  - email         │  │  - nombre_fiscal │  │  - historial     │
│  - password_hash │  │  - dirección     │  │  - satisfacción  │
│  - roles         │  │  - método_pago   │  │  - prioridad     │
└──────────────────┘  └──────────────────┘  └──────────────────┘

Comunicación entre contextos

  • APIs bien definidas: Contratos explícitos entre contextos.
  • Eventos de dominio: Un contexto publica eventos que otros consumen.
  • Anti-Corruption Layer (ACL): Capa de traducción que protege a un contexto de cambios en otro.

DTO (Data Transfer Object)

Objeto usado para transferir datos entre capas o sistemas. No necesariamente representa el modelo interno del dominio.

Contrato

Definición explícita de cómo dos partes se comunican. Puede definir:

  • Estructura de request y response
  • Campos obligatorios y opcionales
  • Tipos de datos
  • Errores posibles
  • Reglas de versionado

Un contrato reduce ambigüedad y ayuda a evolucionar sistemas sin romper consumidores.

Evento

Hecho relevante que ya ocurrió en el sistema. Por ejemplo: OrderPlaced, UserCreated, PaymentConfirmed.

Los eventos son inmutables y fundamentales en arquitecturas event-driven.

Integración

Conexión entre sistemas o componentes distintos, ya sea internos o externos.

Consistencia

Grado en que los datos reflejan correctamente el estado del sistema.

Consistencia fuerte

Todos ven el mismo estado inmediatamente.

Consistencia eventual

Puede haber desfases temporales hasta que el sistema converge.

Idempotencia

Capacidad de ejecutar una operación varias veces y obtener el mismo resultado sin efectos duplicados no deseados.

Acoplamiento

El acoplamiento mide cuánto depende un componente de otro.

Tipos de acoplamiento (de mayor a menor)

  • Acoplamiento de contenido: Un módulo modifica directamente los datos internos de otro. Es el peor tipo.
  • Acoplamiento común: Dos módulos comparten variables globales o estado mutable.
  • Acoplamiento de control: Un módulo le dice a otro cómo hacer su trabajo (pasando flags de control).
  • Acoplamiento de datos: Los módulos se comunican solo a través de parámetros bien definidos. Es el tipo deseable.

Ejemplo práctico

// Alto acoplamiento: el servicio de pedidos conoce
// la estructura interna del servicio de usuarios
function crearPedido(usuarioId) {
  const usuario = db.query('SELECT * FROM usuarios WHERE id = ?', usuarioId);
  if (usuario.saldo_cuenta > 0) {
    // Accede directamente a la tabla de otro dominio
  }
}

// Bajo acoplamiento: comunicación a través de una interfaz
function crearPedido(usuarioId) {
  const puedeComprar = await servicioUsuarios.verificarCapacidadCompra(usuarioId);
  if (puedeComprar) {
    // No conoce los detalles internos del servicio de usuarios
  }
}

Busca bajo acoplamiento entre módulos. Cada componente debe poder evolucionar de forma independiente.

Cohesión

La cohesión mide cuánto se relacionan las responsabilidades dentro de un mismo componente. Alta cohesión significa que todo lo que hace un módulo está estrechamente relacionado.

Tipos de cohesión (de menor a mayor)

  • Coincidental: Las funciones están juntas por casualidad, sin relación lógica.
  • Temporal: Las funciones se agrupan porque se ejecutan al mismo tiempo.
  • Funcional: Todas las funciones contribuyen a una única responsabilidad bien definida. Es el tipo ideal.

Ejemplo práctico

// Baja cohesión: este módulo hace demasiadas cosas no relacionadas
const utils = {
  formatearFecha(fecha) { /* ... */ },
  calcularImpuesto(monto) { /* ... */ },
  enviarEmail(destinatario, asunto) { /* ... */ },
  validarRUT(rut) { /* ... */ },
};

// Alta cohesión: cada módulo tiene una responsabilidad clara
const formatoFechas = {
  formatear(fecha, formato) { /* ... */ },
  parsear(texto, formato) { /* ... */ },
  diferencia(fechaA, fechaB) { /* ... */ },
};

La relación acoplamiento-cohesión

Estos dos conceptos están inversamente relacionados: cuando aumentas la cohesión dentro de un módulo, naturalmente reduces el acoplamiento entre módulos.

Separación de responsabilidades (Separation of Concerns)

Cada parte del sistema debe encargarse de una sola preocupación. En arquitectura, esto se manifiesta en capas y módulos con responsabilidades claras.

Ejemplo en capas

┌─────────────────────────┐
│   Presentación (UI)     │  → Cómo se muestra la información
├─────────────────────────┤
│   Lógica de negocio     │  → Qué reglas aplican
├─────────────────────────┤
│   Acceso a datos        │  → Dónde y cómo se almacenan los datos
└─────────────────────────┘

Principios SOLID en arquitectura

Los principios SOLID, originalmente formulados para diseño orientado a objetos, se aplican también a nivel arquitectónico:

S — Single Responsibility (Responsabilidad Única)

Cada servicio o módulo debe tener una razón para cambiar. Si un cambio en las reglas de facturación requiere modificar el servicio de usuarios, hay un problema de diseño.

O — Open/Closed (Abierto/Cerrado)

El sistema debe ser extensible sin modificar lo existente. Los plugins, middlewares y event-driven architectures facilitan esto.

L — Liskov Substitution (Sustitución de Liskov)

Si un servicio implementa un contrato (API), cualquier implementación alternativa debe ser intercambiable sin romper a los consumidores.

I — Interface Segregation (Segregación de Interfaces)

Los consumidores no deben depender de interfaces que no usan. El patrón BFF (Backend for Frontend) es un ejemplo directo de este principio.

D — Dependency Inversion (Inversión de Dependencias)

Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones.

// Violación: la lógica de negocio depende de PostgreSQL
class ServicioPedidos {
  constructor() {
    this.db = new PostgresClient();
  }
}

// Correcto: depende de una abstracción
class ServicioPedidos {
  constructor(repositorio) {
    this.repositorio = repositorio; // Puede ser Postgres, Mongo, en memoria...
  }
}

Resumen práctico

ConceptoPregunta claveObjetivo
Dominio¿Qué problema de negocio resolvemos?Entender el contexto
Bounded Context¿Dónde están los límites del dominio?Modelos claros y autónomos
Contrato¿Cómo se comunican las partes?Comunicación explícita
Acoplamiento¿Cuánto depende A de B?Minimizar dependencias
Cohesión¿Qué tan relacionado está lo que hace A?Maximizar relación interna
Consistencia¿Los datos reflejan el estado real?Gestionar expectativas
Idempotencia¿Puedo repetir sin efectos duplicados?Operaciones seguras
SOLID¿El diseño es extensible y mantenible?Flexibilidad a largo plazo

Estos conceptos no son reglas absolutas sino guías para tomar mejores decisiones. Cuanto más claro sea este vocabulario, más fácil será entender decisiones de diseño más avanzadas.