Contractes de dades: definir què esperes ABANS que arribin
Tot el que s'ha vist fins ara (pytest, validadors Python, Pandera, Great Expectations) comparteix una limitació: valida després que les dades ja han arribat. Si l'equip que produeix les dades canvia el format d'una columna sense avisar, el pipeline consumidor descobreix el problema quan ja ha fallat una validació — normalment amb el pipeline aturat i algú despertant-se de matinada per una alerta.
Un contracte de dades (data contract) inverteix aquesta lògica: és un acord explícit i versionat entre qui produeix una dada i qui la consumeix, definit abans que la dada flueixi, que especifica quina forma tindrà i quines garanties ofereix.
flowchart LR
P["Equip productor\n(p. ex. equip de\nProducte)"] -->|"publica segons\nel contracte"| C["Contracte de dades\n(schema + SLA + owner)"]
C -->|"consumeix confiant\nen el contracte"| Co["Equip consumidor\n(p. ex. equip\nd'Analítica)"]
C -.->|"canvi de versió\nnotificat abans"| P
style P fill:#1d4ed8,color:#ffffff,stroke:#3b82f6
style C fill:#7c3aed,color:#ffffff,stroke:#a78bfa
style Co fill:#166534,color:#ffffff,stroke:#22c55e
Per què un contracte i no només un esquema
Un esquema (com els que hem definit amb Pandera o Great Expectations) descriu la forma de les dades: tipus, nul·labilitat, rangs. Un contracte de dades va més enllà i inclou:
- El schema: igual que abans (camps, tipus, restriccions).
- La semàntica: què significa realment cada camp (
importés en euros amb IVA inclòs o sense?). - Les garanties de servei (SLA): amb quina freqüència s'actualitzen les dades, quina és la finestra d'entrega esperada.
- El propietari (owner): qui és responsable de mantenir la dada i a qui contactar si hi ha un problema.
- La política de versionat: com i quan es poden fer canvis, i com es comuniquen als consumidors.
Exemple de contracte de dades (YAML)
# contracte_vendes.yml
nom: vendes
versio: "2.1.0"
propietari: equip-ecommerce@empresa.cat
descripcio: >
Un registre per cada venda completada a la botiga online.
Publicat cada 15 minuts a la cua Kafka "vendes.completades".
sla:
frescor_maxima: "20 minuts"
disponibilitat: "99.5%"
schema:
- camp: id_venda
tipus: integer
nullable: false
unic: true
descripcio: "Identificador intern de la venda, autoincremental."
- camp: data_venda
tipus: timestamp
nullable: false
descripcio: "Timestamp UTC en què s'ha completat el pagament."
- camp: import_total
tipus: decimal(10,2)
nullable: false
descripcio: "Import EN EUROS, IVA inclòs."
- camp: pais_client
tipus: string
nullable: false
valors_acceptats: ["ES", "FR", "DE", "IT", "PT"]
politica_versionat:
canvis_no_trencadors: "afegir camps opcionals nous no incrementa la versió major"
canvis_trencadors: "eliminar/renombrar camps, o canviar un tipus, incrementa la versió major i requereix 30 dies de preavís"
Canvis trencadors (breaking) vs no trencadors
| Tipus de canvi | Trencador? | Exemple |
|---|---|---|
| Afegir un camp opcional nou | No | Afegir codi_promocio (nullable) |
| Afegir un valor nou a una llista tancada | Sovint sí, si el consumidor fa un switch/CASE sobre els valors coneguts |
Afegir "UK" a pais_client |
| Canviar el tipus d'un camp existent | Sí | import_total de float a string |
| Renombrar un camp | Sí | import_total → total_amb_iva |
| Eliminar un camp | Sí | Eliminar email_client |
| Canviar la unitat o semàntica sense canviar el nom | Sí (el més perillós, perquè no el detecta cap validador de schema) | import_total deixa de ser "amb IVA" i passa a ser "sense IVA" |
El darrer cas és el motiu principal pel qual un contracte necessita documentar la semàntica, no només el tipus: cap eina de validació de schema (GE, Pandera) detecta que el significat d'un camp ha canviat si el tipus de dada es manté igual.
Versionat semàntic aplicat a dades
De la mateixa manera que una API REST o una llibreria de software fan servir semantic versioning (MAJOR.MINOR.PATCH), un contracte de dades pot adoptar la mateixa convenció:
- MAJOR (
1.x.x → 2.0.0): canvi trencador. Requereix coordinació i preavís als consumidors. - MINOR (
1.1.x → 1.2.0): afegir capacitat de forma compatible (nou camp opcional). - PATCH (
1.1.1 → 1.1.2): correccions que no afecten l'estructura (arreglar un bug de generació que produïa valors nuls inesperats).
flowchart TD
A["v1.0.0\nschema inicial"] -->|"+ camp opcional"| B["v1.1.0"]
B -->|"fix bug generació"| C["v1.1.1"]
C -->|"elimina un camp\n(BREAKING)"| D["v2.0.0"]
style A fill:#166534,color:#ffffff,stroke:#22c55e
style B fill:#166534,color:#ffffff,stroke:#22c55e
style C fill:#166534,color:#ffffff,stroke:#22c55e
style D fill:#7f1d1d,color:#ffffff,stroke:#ef4444
De la teoria a la pràctica
Un contracte de dades es pot fer complir de dues maneres complementàries:
- Validació estàtica: un test (amb pytest, Great Expectations o Pandera) que comprova que el schema real d'un lot de dades coincideix amb el que declara el contracte, executat abans de publicar-lo.
- Validació en temps d'execució amb Schema Registry: en sistemes de streaming (Kafka), un Schema Registry rebutja directament qualsevol missatge que no compleixi el schema registrat, abans fins i tot que arribi a cap consumidor.
import yaml
import pandera as pa
from pandera import Column, DataFrameSchema, Check
def carrega_contracte_com_a_esquema_pandera(ruta_yaml: str) -> DataFrameSchema:
"""Tradueix un contracte YAML a un esquema Pandera per validar-lo automàticament."""
with open(ruta_yaml, encoding="utf-8") as f:
contracte = yaml.safe_load(f)
tipus_map = {"integer": int, "decimal(10,2)": float, "string": str, "timestamp": "datetime64[ns]"}
columnes = {}
for camp in contracte["schema"]:
checks = []
if "valors_acceptats" in camp:
checks.append(Check.isin(camp["valors_acceptats"]))
columnes[camp["camp"]] = Column(
dtype=tipus_map[camp["tipus"]],
nullable=camp["nullable"],
unique=camp.get("unic", False),
checks=checks or None,
)
return DataFrameSchema(columns=columnes, coerce=True)
esquema = carrega_contracte_com_a_esquema_pandera("contracte_vendes.yml")
df_validat = esquema.validate(df, lazy=True)
Aquest patró —generar l'esquema de validació a partir del contracte, en lloc d'escriure'l dues vegades per separat— és clau: evita que el contracte (la documentació) i la validació real (el codi) es desincronitzin amb el temps, un problema molt similar al que aborda la skill validar_sincronitzacio d'aquest mateix repositori per a la programació docent.
Recursos de referència
L'especificació oberta datacontract.com (impulsada, entre d'altres, per Andrew Jones) defineix un format YAML estàndard per a contractes de dades, més complet que l'exemple simplificat d'aquesta pàgina, i és el punt de partida recomanat si un equip vol adoptar contractes de dades de forma seriosa.
Mòdul M5074 Sistemes de Big Data | Institut Sa Palomera (Blanes) | Curs CEIABD 2026-2027