---
id: "ADR-000"
title: "Arquitectura en cuatro capas y organización de dominios"
date: "2026-04-12"
status: "active"
superseded_by: ""
tags: ["architecture", "layers", "domain", "service", "core", "data", "platform"]
summary: "En el contexto de los microservicios White Label, frente a la necesidad de
establecer cómo se organiza el código para que cualquier engineer pueda orientarse
en un servicio que nunca vio, decidimos organizar cada dominio en cuatro capas
(Service, Core, Data, Platform) con dependencias unidireccionales y un archivo
por caso de uso, para lograr consistencia total entre microservicios y claridad
sobre dónde vive cada responsabilidad, aceptando que la estructura es más
explícita y verbal que un enfoque orgánico."
---

## Contexto

White Label es una capa de orquestación que coordina sistemas externos (VTEX, Cognito,
NATS, MongoDB, Redis) y gestiona datos propios del usuario para múltiples marcas.
La complejidad está en coordinar sistemas externos, gestionar diferencias entre proveedores
y soportar múltiples tenants sobre la misma base de código.

Sin una estructura de capas explícita y compartida, cada microservicio desarrolla sus
propias convenciones. Un engineer que abre un servicio que nunca vio necesita tiempo
para orientarse. Un agente de IA tiene el mismo problema: sin estructura predecible,
debe inferir patrones de forma ad hoc, con riesgo de inconsistencias.

El modelo ideal es un microservicio por dominio. En la práctica, durante migraciones,
un servicio puede contener más de un dominio — eso es deuda técnica aceptable siempre
que cada dominio mantenga sus capas completamente aisladas.

## Alternativas consideradas

### Opción A — Crecimiento orgánico sin estructura de capas
Dejar que los paquetes emerjan según la necesidad sin convenciones compartidas.

**Por qué se descartó:** Cada servicio quedaría organizado diferente. El tiempo para
orientarse en un servicio nuevo sería alto tanto para humanos como para agentes.
Imposible establecer guardrails útiles sin estructura predecible.

### Opción B — Domain-Driven Design con capa de dominio propia
Entidades de dominio propias, repositorios, value objects, agregados — el modelo DDD completo.

**Por qué se descartó:** White Label es una capa de orquestación, no un sistema con
lógica de dominio rica. El grueso de la complejidad es adaptar sistemas externos,
no modelar un dominio. Agregar entidades propias paralelas al modelo Protobuf crea
una capa de mapeo sin valor real. El principio de la plataforma es precisamente
el opuesto: Protobuf como modelo único.

### Opción C — Arquitectura hexagonal (ports & adapters)
Núcleo de dominio puro rodeado de adaptadores, con inversión de dependencias en todas las fronteras.

**Por qué se descartó:** Introduce interfaces en todos los puntos de cruce entre capas.
El principio de la plataforma es "interfaces mínimas" — solo cuando hay dos o más
implementaciones reales. Las interfaces por principio agregan complejidad sin beneficio.
Además, los structs Protobuf usados directamente en el core violan el aislamiento de
la arquitectura hexagonal pura, creando una tensión irresoluble.

### Opción elegida — Cuatro capas con dependencias unidireccionales
Cuatro capas con responsabilidades claras y dependencias que van en una sola dirección.
La separación se logra por convención de estructura, no por interfaces en todas las fronteras.

## Decisión

We will organize every microservice domain in four layers with unidirectional dependencies:

### Las cuatro capas

```
Service / Consumer
      ↓
    Core
      ↓
    Data
      ↓
  Platform
```

**Service / Consumer**
Es el punto de entrada al dominio. Implementa los servicios gRPC generados por Protobuf
y todos los consumers que reciben mensajes o eventos entrantes — tanto NATS como sistemas
externos (SQS, webhooks, colas).
- Valida que el request tiene la forma correcta.
- Transforma datos si es necesario antes de pasar al Core.
- Construye la respuesta final a partir de lo que el Core devuelve.
- **No toma decisiones de negocio bajo ninguna circunstancia.**
- Los consumers son el equivalente del Service para entradas asíncronas: deserializan y
  delegan al Core. Esto aplica a **todos los tipos de consumer** (NATS, SQS, webhooks).
  Todos viven en `consumer/`.

**Core**
Es donde vive la lógica de orquestación del dominio.
- Una `struct` que recibe como dependencias todo lo que necesita: abstracciones de datos,
  clientes de plataforma, flags de comportamiento y reglas de negocio.
- **Un archivo por caso de uso**: `<usecase>.go` con el método sobre el Core.
- Puede recibir y devolver mensajes Protobuf directamente.
- No sabe nada de HTTP ni gRPC. No sabe si quien lo llama es un handler REST o un consumer.
- Puede retornar errores gRPC directamente cuando se quiere controlar el status code.

**Data**
Contiene las abstracciones sobre sistemas de almacenamiento del dominio y los producers
de eventos salientes.
- Nombres que reflejan la infraestructura: `cart_collection`, `session_storage`,
  `identity_collection`. **Nunca nombres abstractos que oculten la tecnología.**
- Los producers de eventos viven en `data/producer/`: reciben un evento del dominio,
  construyen el CloudEvent y lo publican. Sin lógica de negocio.
- El Core decide cuándo y qué publicar; el producer solo sabe cómo enviarlo.

**Platform**
Contiene todo lo externo al dominio pero necesario para que funcione.
- Clientes crudos de infraestructura: `mongo.Client`, `redis.Client`.
- Clientes a APIs externas: VTEX, Cognito, CMS.
- Configuración, observabilidad (OTel), servidor gRPC.
- **Transversal a todos los dominios**: si dos dominios usan VTEX, ambos usan
  el mismo cliente definido en Platform.

### Estructura de directorios por dominio

```
internal/
  <domain>/
    core/
      core.go           ← struct principal con dependencias, flags y rules
      <usecase>.go      ← un archivo por caso de uso
      rule/             ← function types para reglas de negocio comunes
      <tenant>/         ← implementaciones específicas por tenant
      module.go
    data/
      <store>.go        ← abstracciones sobre DBs con nombres de infraestructura
      producer/         ← publicación de eventos a NATS
      module.go
    service/
      <service>.go      ← handlers gRPC/HTTP
      errors.go
      module.go
    consumer/           ← handlers de mensajes entrantes (NATS, SQS, webhooks — todos aquí)
      module.go
    event/              ← definición de eventos que emite o recibe el dominio
    module.go           ← módulo de dominio, compone data + core + service + consumer
  common/               ← errores y utilidades compartidas entre dominios
  health/               ← healthcheck, dominio especial de infraestructura
  platform/             ← clientes e infraestructura transversal
```

### Regla de comunicación entre dominios

Los dominios **no se llaman entre sí a nivel de código**, ni dentro del mismo microservicio.
Si un dominio necesita reaccionar a algo que ocurrió en otro, lo hace a través de eventos.
Esta restricción existe para que dos dominios que conviven en un microservicio puedan
separarse en microservicios independientes sin rediseño.

### Interfaces

Se usan solo cuando:
- Hay dos o más implementaciones reales del mismo comportamiento, o
- La dependencia crearía un ciclo de importación.

El orden de preferencia para modelar variaciones entre tenants es:
**flag booleano → function type → interface**

## Consecuencias

**Positivas:**
- Cualquier engineer (humano o agente) puede orientarse en un servicio que nunca vio
  siguiendo la misma estructura.
- El Core es el único lugar donde vive la lógica de orquestación: agregar comportamiento
  transversal (logging, auditoría) no requiere tocar Service ni Data.
- La restricción de no comunicación directa entre dominios facilita separar dominios
  en microservicios independientes en el futuro.

**Negativas:**
- La estructura es más explícita y verbose que un enfoque orgánico.
- La regla "interfaces solo cuando hay necesidad concreta" requiere criterio y puede
  generar debate.

**Riesgos y mitigaciones:**
- **Lógica de negocio que se cuela en Service** → La revisión de código y los ADRs
  son la barrera. Un agente que no sigue estas reglas debe ser corregido con `/agdr:reject`.
- **Dominios que crecen sin corte claro** → La señal es cuando un dominio necesita saber
  del estado de otro. Esa es la indicación de que se necesita un evento.

## ADRs relacionados

- ADR-002: Modelo multi-tenant (define cómo se inyectan flags y rules en el Core constructor)
- ADR-003: Comunicación orientada a eventos (define producers en Data y consumers en Service)
