Tres repositorios que no se pisan, un contrato escrito por cada flujo y una única puerta entre ellos. Es lo que se toma de la idea de data mesh, adaptado a un laboratorio pequeño.
Un data mesh plantea que los datos no los gestiona un equipo central sino los dominios que los conocen, que cada dominio publica sus datos como un producto con una interfaz estable, y que el gobierno es federado: unas reglas comunes, aplicadas por cada dominio. Está pensado para organizaciones con muchos equipos. Aquí no hace falta tanto, pero el problema de fondo es el mismo: cambiar una parte sin romper las demás.
Cada repositorio es dueño de lo suyo y no escribe en el de otro. Cada flujo declara qué lee y qué produce. Lo que cruza de un dominio a otro es un producto con nombre, no un fichero que alguien sabe dónde está. Las reglas comunes viven en un documento aparte.
No hay catálogo corporativo ni plataforma de autoservicio. Los dominios comparten una carpeta de datos: la frontera entre ellos es de convención, no de seguridad.
Cada uno tiene su repositorio, sus pruebas y su ciclo de cambios. Los tres siguen la misma convención: sus flujos viven en carpetas por capa y cada uno lleva su contrato. El agente de contenido tardó en entrar en ella (era un conjunto de herramientas de terminal) y lo hizo en octubre: la rutina de después de una jornada, que antes eran varios comandos a mano, es ahora un flujo que llama a los demás en orden, con los mismos comandos por debajo. La web, que no es un flujo de Prefect, declara igualmente qué lee, para que el mapa llegue hasta ella. Un cuarto repositorio, data-governance, no contiene código: contiene la ley. Define las capas, cómo se nombra una entidad, qué clasificación de sensibilidad lleva y quién puede leer qué. Se separa a propósito: quien escribe las reglas no debería ser quien las cumple.
Cada flujo lleva, junto a su código, un fichero de configuración con un bloque data_contract: qué entidades lee, cuáles produce, dónde viven, en qué formato y con qué clasificación, y qué otros flujos lo llaman. Así luce el de uno de los Gold:
# flows/gold/player_games/settings.yaml (recortado)
data_contract:
inputs:
- entity: "silver_player_boxscore"
path: "feb-scouting/silver/official-boxscore/player_boxscore"
format: "delta"
- entity: "silver_derived_minutes"
path: "feb-scouting/silver/derived-minutes/derived_minutes"
format: "delta"
- entity: "gold_matches"
path: "feb-scouting/gold/matches/matches"
format: "delta"
outputs:
- entity: "gold_player_games"
path: "feb-scouting/gold/player-games/player_games"
format: "delta"
partition_by: ["season", "comp_id"]
tier: gold
classification: public
flow_dependencies:
called_by: ["flows/gold/sync/flow.py:main"]Un proyecto aparte, el observatorio, tiene una lista explícita de los dominios del laboratorio. Añadir uno es añadir una línea, siempre que siga la convención. De esa lista lee todos los contratos, aplica las reglas de gobierno a la vez y ve lo que ningún dominio puede ver solo: que lo que cruza una frontera sea Gold del productor.
Juntando todos esos contratos se puede dibujar el laboratorio entero, sin que nadie lo dibuje a mano: el mapa de los datos.
Tres convenciones lo hacen útil. El nombre de la entidad empieza por su capa (bronze_, silver_, gold_), así que nadie confunde un dato crudo con uno certificado. La ruta física sigue siempre el mismo patrón (dominio, capa, flujo, conjunto de datos), de modo que de la ruta se deduce quién lo produce. Y la clasificación se propaga de forma conservadora: un dato derivado hereda la más restrictiva de sus entradas.
La regla central dice que lo que cruza la frontera de un dominio es siempre Gold del productor: nadie lee el Silver ni el Bronze de otro. Y su corolario, que es el que duele: Gold nunca es un ascenso de un Silver que ya existe, sino un artefacto nuevo, delgado, con solo lo que de verdad se consume fuera.
Retirarla no fue cambiar una ruta. Al examinar qué hacía el consumidor con esos datos al leerlos, resultó que corregía cosas que son del productor, y que cualquier consumidor futuro habría tenido que redescubrir:
El acta oficial no da minutos en 2021-22 ni 2022-23, y solo en parte de 2023-24. El consumidor los reconstruía desde las sustituciones. Ahora los reconstruye feb-scouting y los marca como derivados.
El acta numera a los jugadores con ids que no son los del minuto a minuto ni los de otras temporadas. El consumidor lo arreglaba al exportar. Ahora el id estable viene de serie, y el del acta se conserva aparte.
Siete partidos de liga de dos temporadas no los sirve la fuente. Su marcador se copiaba a mano en un fichero del consumidor. Ahora es una fuente manual del productor, con contrato.
Un patrocinador cambia a mitad de curso y el club salía con dos nombres. Y la Copa Princesa vive dentro del identificador de la liga. Los dos se resuelven una vez, en Gold.
Las posiciones de los jugadores siguen la misma lógica por el otro lado. Antes iban a ser un fichero que mantendría el consumidor. Ahora son una fuente manual de Bronze del productor: un CSV versionado, validado al entrar, que pasa por Silver y llega a Gold marcado con su origen (manual o de la plantilla). Lo que es del dato se arregla donde nace el dato.
Mover lógica entre repositorios es el tipo de cambio que sale bien en las pruebas y mal en lo publicado. Se hicieron tres comprobaciones que no dependían de la memoria de nadie:
Todo el almacenamiento es una carpeta de una máquina. La clasificación de cada dato es documentación, no seguridad: cualquier proceso que conozca la ruta puede leer.
El observatorio nació como una plataforma de monitorización, y no es lo que hoy se usa de él. Lo recuperado es lo que sí aporta a esta escala: el registro de dominios, las reglas de gobierno y el mapa. Lo físico (que cada tabla exista y su esquema no haya derivado) existe en el código, pero no se da por vigilado.
Una tabla Gold se reconstruye cuando cambian sus entradas. Si cambia una regla, hay que forzar la reconstrucción a mano. Queda escrito en el repositorio.
Había reglas escritas que nadie hacía cumplir, el equivalente a un contrato firmado y guardado en un cajón. Ahora cada dominio comprueba las suyas en su propia suite (el repositorio de ingesta lo hace con una prueba como la de abajo) y el observatorio las repite sobre todos a la vez, incluidas las que solo se ven entre dos: la de cruce de fronteras y la de un Gold que nadie lee, que suele ser un Silver con otro nombre. Todo ello ya encontró cosas en su primera ejecución: una entidad de control sin prefijo de capa y la errata de arriba.
def test_every_flow_declares_a_contract_with_tiered_outputs(path):
for o in outputs:
assert o["tier"] in ("bronze", "silver", "gold")
assert o["entity"].startswith(f"{o['tier']}_") # el nombre dice la capa
assert o["path"].startswith(f"feb-scouting/{o['tier']}/")
def test_a_gold_flow_reads_only_silver_or_gold(path):
for i in inputs:
assert not i["entity"].startswith("bronze_")A esta escala, el valor de un contrato no es la burocracia: es poder mover el trabajo al sitio que le corresponde sin miedo. Subir la reconstrucción de minutos al productor fue posible porque había una puerta definida por la que servirla, una comparación exacta para demostrar que no cambiaba nada, y un consumidor con pruebas que avisara si algo no encajaba.