Arquitecturas de datos: cómo versionar el esquema cuando el pipeline depende de APIs externas
Arquitecturas de datos: cómo versionar el esquema cuando el pipeline depende de APIs externas
El pipeline lleva semanas estable. Entonces el proveedor de datos publica una nota técnica discreta: "ajuste menor en el formato de respuesta, sin impacto previsto". Cuarenta y ocho horas después el job de transformación empieza a emitir errores silenciosos. Los registros siguen entrando, pero un campo ha cambiado de tipo string a array. El modelo de downstream lo interpreta como nulo. Nadie lo detecta hasta que alguien pregunta por qué las métricas de cobertura llevan dos días en cero.
Este escenario no es excepcional. Es el modo por defecto en entornos donde el esquema de la API externa no está bajo tu control. Y el problema no es que el proveedor cambie el esquema —lo hará— sino que la arquitectura no tiene una estrategia de versionado que absorba ese cambio sin romper producción.
Versionar el esquema propio es higiene básica. Versionar el esquema ajeno que recibes por API es ingeniería defensiva. Son disciplinas distintas y la segunda se ignora con mucha más frecuencia.
El error de tratar el esquema externo como estático
Muchos equipos diseñan el pipeline asumiendo que el contrato de datos del proveedor es una constante. Mapean los campos en el primer sprint, los hardcodean en el transformador y pasan al siguiente problema. Funciona durante meses. Entonces falla.
El fallo no llega siempre como una excepción visible. A veces llega como silencio: un campo que desaparece devuelve null y el pipeline sigue procesando sin quejarse. El dato incompleto viaja hasta el data warehouse. El analista lo descubre tres semanas después cuando intenta cruzar ese campo con otra fuente.
La raíz del problema es arquitectónica: no existe una capa que observe el esquema entrante, lo compare con la versión conocida y decida cómo actuar. Todo el sistema asume que el contrato es inmutable.
Diseñar una capa de validación de esquema en la ingestión
La solución empieza antes de transformar: validar el esquema en el punto de entrada, no en el destino.
El patrón más efectivo es mantener un schema registry propio, independiente del proveedor, donde se almacena la versión canónica esperada de cada respuesta API. Cada vez que llega un payload, se valida contra esa versión antes de que el dato entre al pipeline.
import jsonschema
import json
SCHEMA_REGISTRY = {
"feed_response_v3": {
"type": "object",
"required": ["id", "published_at", "source", "sentiment_score"],
"properties": {
"id": {"type": "string"},
"published_at": {"type": "string", "format": "date-time"},
"source": {"type": "object"},
"sentiment_score": {"type": "number"}
}
}
}
def validate_payload(payload: dict, schema_key: str) -> bool:
try:
jsonschema.validate(instance=payload, schema=SCHEMA_REGISTRY[schema_key])
return True
except jsonschema.ValidationError as e:
log_schema_drift(schema_key, str(e))
return False
Cuando la validación falla, el payload no se descarta automáticamente: se enruta a una dead-letter queue con metadatos del error. El pipeline principal sigue funcionando. El equipo recibe una alerta y tiene tiempo de analizar el drift antes de decidir si actualiza el schema registry o implementa una capa de compatibilidad.
Schema evolution: compatibilidad hacia atrás y hacia adelante
No todos los cambios de esquema rompen el pipeline de la misma manera. Conviene clasificarlos antes de reaccionar:
Cambios compatibles hacia atrás (el código existente sigue funcionando):
- Añadir un campo nuevo opcional.
- Ampliar un tipo de valor (
int→float).
Cambios incompatibles (rompen el código existente):
- Eliminar un campo requerido.
- Cambiar el tipo de un campo existente (
string→array). - Renombrar un campo sin alias.
El schema registry debe almacenar todas las versiones históricas, no solo la última. Así el pipeline puede activar un modo de compatibilidad cuando detecta que el payload entrante corresponde a una versión anterior conocida, en lugar de rechazarlo directamente.
def detect_schema_version(payload: dict) -> str:
# Heurística simple: presencia de campos discriminadores
if "sentiment_score" in payload and isinstance(payload["sentiment_score"], list):
return "feed_response_v4"
elif "sentiment_score" in payload:
return "feed_response_v3"
return "feed_response_unknown"
Este enfoque permite mantener varios transformadores activos en paralelo —uno por versión— y enrutar cada payload al transformador correcto sin detener la ingestión.
Alertas de drift: monitorizar la desviación antes de que llegue el error
La validación puntual no es suficiente. Los cambios de esquema a veces son graduales: el proveedor introduce un campo nuevo en el 10 % de los registros durante días antes de extenderlo al 100 %. Si solo validas el esquema en binario —válido / inválido— ese drift pasa desapercibido.
Una capa de monitorización estadística del esquema resuelve esto. La idea es medir, para cada campo, la tasa de presencia, el tipo observado y la distribución de valores en ventanas de tiempo. Cuando esa distribución se desvía del histórico más allá de un umbral, se dispara una alerta.
Métricas mínimas a rastrear por campo:
presence_rate: porcentaje de registros donde el campo está presente.type_consistency: porcentaje de registros donde el tipo coincide con el esperado.null_rate: porcentaje de valores nulos o vacíos.
Un campo que pasa de presence_rate: 0.98 a presence_rate: 0.61 en 24 horas es una señal de cambio en el proveedor, aunque la validación formal no haya fallado todavía. Detectarlo en ese momento —y no una semana después— es la diferencia entre un ajuste de media hora y una auditoría de datos retroactiva.
En entornos donde el volumen de señales es alto, como los pipelines que consumen fuentes del universo público de Internet a través de APIs como FeedScale, esta monitorización estadística es especialmente crítica: un drift en el esquema de una fuente de alta frecuencia puede contaminar miles de registros antes de que el error sea visible en las métricas de negocio.
Documentar el contrato como parte del pipeline, no como tarea pendiente
El schema registry no es solo una herramienta técnica. Es la documentación viva del contrato con el proveedor. Cada versión del esquema debe incluir:
- Fecha de detección de la versión.
- Diferencia respecto a la versión anterior (campos añadidos, modificados, eliminados).
- Decisión de compatibilidad tomada por el equipo (modo de compatibilidad activado, transformador actualizado, etc.).
- Fecha de deprecación de la versión anterior, si se fijó.
Este registro convierte lo que suele ser conocimiento tribal —"ah, eso lo cambió el proveedor en febrero"— en un artefacto trazable que cualquier miembro del equipo puede consultar. Y cuando el proveedor vuelve a cambiar el esquema —porque lo hará— el equipo tiene el contexto necesario para actuar en minutos, no en días.
El esquema externo nunca será tu aliado estable. Pero una arquitectura que lo trata como variable —y no como constante— convierte cada cambio del proveedor en un evento gestionado, no en una crisis. Esa diferencia es la que separa un pipeline frágil de uno que aguanta en producción.