Saltar a contenido

0019. Versionado de eventos de integración (outbox / Kafka)

Estado: Propuesto Fecha: 2026-09-08 Autor: Duval Alcivar Módulo(s) afectado(s): svc-identidad, consumidores de eventos (ERP, CRM)

Contexto

El alta de empresa (0008) avisa a los demás sistemas con un evento (empresa.creada, sucursal.creada) vía outbox; hoy se entrega por HTTP y mañana por Kafka, "sin cambiar el mensaje". Pero un mensaje que crece con los meses va a cambiar de contenido (se agrega un campo, se renombra otro), y sin versión los consumidores no saben qué contrato están leyendo → roturas en caliente y acoplamiento tácito. Falta la regla de versionado que aplica la misma disciplina del contrato de API (transversal 0016) a los eventos.

Decisión

Todo evento publicado por svc-identidad lleva versión explícita desde hoy (empresa.creada.v1), un payload validado contra JSON Schema versionado en el repositorio, y reglas de compatibilidad idénticas a las de la API (transversal 0016).

1. Nombre del evento = contrato versionado

<entidad>.<verbo>.vN      ejemplo:  empresa.creada.v1, sucursal.creada.v1

La versión siempre es explícita desde el primer evento (nada de "v1 implícito y se agrega v2 cuando rompa"): los consumidores parsean y enrutan con una regla uniforme, y el nombre del tópico no cambia cuando cambia el contrato.

El envelope se mantiene como lo definió el 0008 (el campo evento actualizado con el sufijo .vN, más trace_id y payload), y se añade dataversion por si un mismo transporte lleva varias versiones:

{
  "evento": "empresa.creada.v1",        // nombre + versión
  "dataversion": "1",
  "trace_id": "<uuid>",
  "payload": {
    "empresa_id": 1,
    "nombre": "Empresa Demo",
    "identificacion": "0999999999001",
    "sucursales": [],
    "aplicaciones": ["erp", "crm"]
  }
}

2. Schemas versionados en el repo

Cada evento versionado tiene su JSON Schema en el repositorio: schemas/events/empresa.creada.v1.schema.json. La publicación valida el payload contra el schema (fallo de validación = evento no se publica) y los consumidores pueden validar también al recibir. El CI valida los schemas contra openapi-spec-validator-equivalente para JSON Schema (mismo espíritu del transversal 0017 aplicado a eventos).

3. Reglas de compatibilidad (mismas que la API)

Cambio en payload ¿Rompe? Acción
Añadir campo opcional (con default) no se documenta, misma versión
Añadir campo requerido nueva versión (empresa.creada.v2)
Renombrar / cambiar tipo / quitar campo nueva versión
Cambiar semántica (ej. cambio de moneda de un monto) nueva versión

4. Coexistencia y retiro

  • Una versión nueva no reemplaza la vieja de golpe: ambos se publican durante la transición (productor emite v2 y sigue emitiendo v1 mientras haya consumidores pendientes), igual que la deprecación de la API (0016).
  • Se retira v1 cuando todos los consumidores migraron (métrica de consumo en 0 durante la ventana), y se anota su estado deprecatedretired en un registro de versiones junto a los schemas.
  • Cada versión queda como archivo en schemas/events/ para auditoría; nunca se sobrescribe un schema histórico.

5. Cuando llegue Kafka

  • Tópico por entidad: identidad.empresa (no uno por versión).
  • key = empresa_id (orden por entidad, partición estable por empresa).
  • value = el envelope del §1; los consumidores filtran por evento (nombre + versión).
  • El canal cambia (HTTP → Kafka), el contrato no: por eso se versiona ya.

Alternativas descartadas

  • Avro + Schema Registry (Confluent) — potente pero impone toolchain y compat-broker extra; JSON Schema ya es el estándar del proyecto (transversal 0017) y suficiente para nuestro volumen de eventos.
  • Protobuf — igual que Avro; añade generadores de código en cada consumidor sin ganancia para eventos lentos (onboarding de empresas).
  • Un tópico/endpoint por versión (empresa.creada.v2 como tipo aparte) — multiplica el fanout y el enrutamiento; la versión es un atributo del evento, no un canal.
  • Sin versionado (se edita el payload en el lugar) — los consumidores viejos rompen sin aviso; no hay manera de retirar un contrato.
  • Versionar solo en la doc, no en el nombre — sin propiedad explícita el consumidor no puede enrutar ni el CI puede comparar versiones.

Consecuencias

  • empresa.creada pasa a empresa.creada.v1 desde ahora; el salto HTTP→Kafka ya no toca el contrato (solo el transporte), como prometió el 0008.
  • Cualquier cambio de payload exige un schema nuevo + validación CI + ventana de coexistencia: la rotura de consumidores pasa a ser un evento raro y planificado.
  • Los consumidores validan contra los schemas del repo; un evento inválido genera alerta en el productor (no se publica) y en el consumidor (se tacha).
  • Queda pendiente (no de svc-identidad): el transversal de mensajería que formalice el mismo reglamento para todos los servicios, y el registro central de versiones con su UI/estado.

Este ADR formaliza la promesa del 0008 (mensaje estable, outbox) y el ciclo de vida de empresa del 0016 con reglas de versionado iguales al 0016 transversal (API). La CI que valida schemas es la extensión del 0017 transversal a eventos.