Módulo 7: Messy Domains And Medallion At Depth

Presentación del módulo: el dominio feo de Kiosko, con nombre y con contrato

Por qué existe este módulo

Detente un momento en lo que Kiosko ya tiene, sin que este módulo haya construido todavía ni una sola tabla nueva. fact_orders (transaccional, módulo 1), fact_sessions (accumulating snapshot, módulo 6) y fact_store_activity (cumulative, módulo 6) son tres tablas de hechos, no una. dim_store, dim_product/dim_product_scd (historizada, módulo 4) y dim_date (conformada, módulo 2) son tres dimensiones con formas distintas —una simple, una historizada, una con llave inteligente—. Y ya viste, desde el módulo 1, que order_id vive dentro de fact_orders sin tener tabla propia: la primera dimensión degenerada de esta guía, nombrada pero no desarrollada a fondo. Ningún libro de texto de modelado dimensional abre con este panorama. Casi todos abren con el ejemplo de juguete: un hecho, dos dimensiones, un JOIN limpio. Ese ejemplo es real y necesario —así abrió el módulo 2 de esta guía—, pero no es lo que un equipo de datos encuentra el segundo mes de trabajo. Encuentra esto: varios hechos, varios tipos de dimensión, y ninguna garantía automática de que las piezas nuevas respeten la forma de las piezas viejas.

Este módulo le pone nombre formal a ese panorama —un dominio feo, en el sentido preciso de Kimball: no “mal diseñado”, sino “con más de un proceso de negocio conviviendo”— y construye dos piezas que ningún módulo anterior necesitó. Primero, la dimensión junk: cuando Kiosko empieza a registrar payment_method y channel por cada orden —dos atributos de baja cardinalidad que este módulo introduce—, la pregunta no es solo “¿cómo los guardo?”, sino “¿los guardo como dos columnas sueltas, o los agrupo en una sola dimensión pequeña, con un solo flag_key?”. Segundo, el contrato Medallion: con tres hechos y cuatro tablas gold publicadas, ¿cómo confirmas, con evidencia y no con revisión manual, que cada una mantiene exactamente las columnas que el resto de la guía —y cualquier consumidor de BI— espera? La respuesta de este módulo es validate_gold_schema(), una función de Python que compara el esquema real contra el esperado, corrida sobre las cuatro tablas gold de esta guía.

Conexión con el módulo. Este módulo no modifica ni una fila de fact_orders, dim_store, dim_product, dim_product_scd, dim_date, fact_sessions ni fact_store_activity — las siete tablas que los módulos 1 a 6 dejaron completas y verificadas. Lo que construye es nuevo: dim_order_flags (dimensión junk), la profundización completa de la dimensión degenerada ya nombrada en el módulo 1, y validate_gold_schema(), la función que formaliza el contrato entre las capas bronze, silver y gold que esta guía —y foundations, antes que ella— ya usa desde el principio, sin haberlo puesto nunca por escrito como una regla verificable.

Una analogía: el inventario de la bodega, no la lista de compras de un solo producto

Piensa en la diferencia entre la lista de compras de una sola persona —leche, pan, huevos— y el inventario completo de una bodega de un supermercado grande: cientos de referencias, cada una con su propia categoría, su propia rotación, algunas que llegan a diario y otras que llegan una vez al mes, algunas que se venden sueltas y otras que solo tienen sentido agrupadas en un combo. Nadie diseña el sistema de inventario de una bodega grande pensando en una sola referencia — se diseña pensando en que van a convivir referencias de naturaleza distinta, y en que el sistema necesita reglas explícitas —un contrato— para que un empleado nuevo, o un proveedor nuevo, no rompa el inventario completo por accidente al agregar una referencia con el formato equivocado.

Los módulos 1 a 6 de esta guía construyeron, uno a la vez, cada "referencia" del inventario de Kiosko: el grano de una venta, el calendario, la forma del star, la historia de un producto, el join correcto en el tiempo, dos tipos de hecho no transaccionales. Este módulo es el momento de tratar todo eso como lo que ya es: un inventario completo, con reglas explícitas sobre qué forma puede tener cada pieza nueva antes de que se le permita entrar al almacén gold.

Ejemplo trabajado: el inventario completo de Kiosko, antes de tocar código nuevo

Antes de construir nada, el mismo tipo de mapa que abrieron los módulos 3, 4 y 6 antes de su patrón central — esta vez, un inventario de lo que Kiosko ya tiene, clasificado por tipo:

# domain_inventory.py
KIOSKO_DOMAIN = [
    ("fact_orders",         "hecho transaccional",       "modulo 1", 40, "order_id (degenerada), store_id (FK), product_id (FK)"),
    ("fact_sessions",       "accumulating snapshot",     "modulo 6", 17, "session_id (PK), store_id (FK)"),
    ("fact_store_activity", "cumulative table design",   "modulo 6", 21, "store_id (FK), activity_date"),
    ("dim_store",           "dimension simple",          "modulo 1",  3, "store_key (PK sustituta)"),
    ("dim_product",         "dimension simple (star)",   "modulo 2",  4, "product_key (PK sustituta)"),
    ("dim_product_scd",     "dimension historizada SCD-2", "modulo 4", 5, "product_key (PK sustituta), valid_from/valid_to"),
    ("dim_date",            "dimension conformada",      "modulo 2", 31, "date_key (PK, llave inteligente)"),
]

print("=== El dominio de Kiosko, tal como lo dejo el modulo 6 ===\n")
print(f"{'tabla':22} {'tipo':28} {'origen':12} {'filas':>6}   {'llaves'}")
for table, kind, origin, rows, keys in KIOSKO_DOMAIN:
    print(f"{table:22} {kind:28} {origin:12} {rows:6}   {keys}")

fact_tables = [t for t, kind, *_ in KIOSKO_DOMAIN if kind.startswith("hecho") or "cumulative" in kind or "accumulating" in kind]
dim_tables = [t for t, kind, *_ in KIOSKO_DOMAIN if "dimension" in kind]
print(f"\nTotal de tablas de hechos: {len(fact_tables)} -> {fact_tables}")
print(f"Total de tablas de dimension: {len(dim_tables)} -> {dim_tables}")
print("\nLo que TODAVIA no existe: una dimension junk (payment_method + channel), y una funcion")
print("que confirme, con evidencia, que las 4 tablas gold mantienen el esquema que este dominio necesita.")

Qué esperar. Al correr python3 domain_inventory.py, la salida es exactamente esta:

=== El dominio de Kiosko, tal como lo dejo el modulo 6 ===

tabla                  tipo                         origen        filas   llaves
fact_orders            hecho transaccional          modulo 1         40   order_id (degenerada), store_id (FK), product_id (FK)
fact_sessions          accumulating snapshot        modulo 6         17   session_id (PK), store_id (FK)
fact_store_activity    cumulative table design      modulo 6         21   store_id (FK), activity_date
dim_store              dimension simple             modulo 1          3   store_key (PK sustituta)
dim_product            dimension simple (star)      modulo 2          4   product_key (PK sustituta)
dim_product_scd        dimension historizada SCD-2  modulo 4          5   product_key (PK sustituta), valid_from/valid_to
dim_date               dimension conformada         modulo 2         31   date_key (PK, llave inteligente)

Total de tablas de hechos: 3 -> ['fact_orders', 'fact_sessions', 'fact_store_activity']
Total de tablas de dimension: 4 -> ['dim_store', 'dim_product', 'dim_product_scd', 'dim_date']

Lo que TODAVIA no existe: una dimension junk (payment_method + channel), y una funcion
que confirme, con evidencia, que las 4 tablas gold mantienen el esquema que este dominio necesita.

Tres hechos, cuatro dimensiones, siete tablas en total — y esto es, precisamente, lo que un libro de texto de "un hecho, dos dimensiones" nunca prepara a nadie para leer. Fíjate en que ninguna de las siete filas de esta tabla es "la forma correcta" de una tabla de Kiosko — cada una tiene el tipo exacto que su proceso de negocio necesitaba: transaccional para una venta puntual, accumulating snapshot para un proceso que avanza, cumulative para una métrica que se acumula, historizada para un catálogo que cambia. Ese es, con precisión, el "dominio feo" del que habla el título de este módulo — feo no porque esté mal hecho, sino porque no cabe en el diagrama de una sola estrella.

Diagrama: dónde estabas, dónde vas a estar

flowchart LR
    subgraph M16["Modulos 1-6 (ya escritos)"]
        A["3 hechos + 4 dimensiones\nverificados, EJECUTADO"]
    end

    subgraph M7["Este modulo (7 de 8)"]
        B["L2: Inventario del dominio\nEJECUTADO"]
        C["L3: Dimension degenerada\na fondo, EJECUTADO"]
        D["L4: dim_order_flags\ndimension junk, EJECUTADO"]
        E["L5: Contrato Medallion\nvalidate_gold_schema(), EJECUTADO"]
        F["L6: dim_date, un calendario\npara 3 hechos, EJECUTADO"]
        G["L7: Evolucion de esquema\nsin romper gold, EJECUTADO"]
        H["L8: Proyecto integrado\nEJECUTADO"]
    end

    subgraph M8["Modulo 8 (capstone)"]
        I["Warehouse completo\nde punta a punta"]
    end

    A --> B --> C --> D --> E --> F --> G --> H --> I

El mapa de este módulo

Leccion   Que construye
────────  ──────────────────────────────────────────────────────────────
L1        (esta) El inventario del dominio, antes de construir nada nuevo
L2        Cuando un hecho y dos dimensiones no alcanzan, EJECUTADO
L3        Dimensiones degeneradas: order_id, a fondo, EJECUTADO
L4        Dimensiones junk: dim_order_flags, EJECUTADO
L5        Contratos Medallion entre bronze/silver/gold, EJECUTADO
L6        Multiples hechos, un solo calendario conformado, EJECUTADO
L7        Evolucion de esquema sin romper gold, EJECUTADO
L8        Proyecto: la capa gold multi-hecho de Kiosko, EJECUTADO

Profundización: por qué "dominio feo" es un término técnico, no un insulto

Vale la pena ser precisos con el vocabulario, porque "feo" suena a defecto y no lo es. Kimball nunca usa esa palabra exacta, pero el concepto que describe —un bus matrix con múltiples procesos de negocio, cada uno con su propia tabla de hechos, compartiendo un subconjunto de dimensiones conformadas— es, literalmente, la descripción de cualquier warehouse de producción real. El módulo 2 de esta guía ya construyó el primer renglón de ese bus matrix (la venta, con dim_store/dim_product/dim_date); el módulo 6 agregó dos renglones más (las sesiones, la actividad diaria). Un bus matrix con un solo renglón no es "limpio" — es incompleto, porque casi ningún negocio real tiene un solo proceso que valga la pena medir.

La palabra "feo" en el título de este módulo describe, con precisión, la experiencia de trabajar en ese dominio sin las herramientas correctas: columnas sueltas de baja cardinalidad multiplicándose sin control (el problema que resuelve la dimensión junk), identificadores que alguien intenta convertir en tablas propias sin necesitarlo (el problema que resuelve nombrar bien la dimensión degenerada), y esquemas que cambian sin que nadie se entere hasta que un reporte se rompe en producción (el problema que resuelve el contrato Medallion). Ninguno de los tres es un problema de "mal diseño" — son, los tres, consecuencias normales de tener éxito: más procesos de negocio que medir, más atributos que capturar, más gente tocando el mismo warehouse. Este módulo no elimina esa complejidad — le da nombre, estructura y, en el caso del contrato, verificación automática.

Errores comunes

Pensar que "dominio feo" significa que el modelo de los módulos 1-6 estuvo mal diseñado. Qué pasa: alguien, al leer el título de este módulo, concluye que fact_orders, fact_sessions y fact_store_activity deberían haberse diseñado distinto desde el principio para evitar la complejidad que este módulo nombra. Por qué pasa: la palabra "feo" en el título invita, de forma natural, a buscar un culpable o un error previo. Cómo detectarlo: si tu conclusión al terminar esta lección es que algún módulo anterior "debió preverlo", perdiste el argumento central — no hay ninguna forma de diseñar fact_orders en el módulo 1 que evite que, seis módulos después, Kiosko tenga tres hechos en vez de uno; eso es simplemente lo que pasa cuando un negocio real crece. Cómo corregirlo: "dominio feo" es una etiqueta descriptiva, no una crítica — describe la convivencia de varios procesos de negocio, exactamente lo que Kimball predice y lo que cualquier warehouse de producción tiene. El objetivo de este módulo no es "arreglar" los módulos anteriores, sino darle nombre y contrato a algo que ya era correcto.

Asumir que este módulo va a consolidar los tres hechos en uno solo. Qué pasa: alguien, al ver la palabra "conviviendo" en la descripción del módulo, espera que la lección 6 o el proyecto final fusionen fact_orders, fact_sessions y fact_store_activity en una sola tabla más grande. Por qué pasa: "hacer convivir" suena, en lenguaje cotidiano, a "unir en una sola cosa". Cómo detectarlo: si esperas encontrar, en algún punto de este módulo, un UNION o un JOIN que combine las tres tablas de hechos en una sola fila por evento, no lo vas a encontrar — cada una mantiene su propio grano, su propia tabla, su propio tipo. Cómo corregirlo: "convivir" en este módulo significa "compartir dimensiones conformadas" (la lección 6 lo demuestra con dim_date), no "fusionarse en una tabla". Tres hechos con tres grano distintos nunca deberían combinarse en una sola tabla — eso rompería, de inmediato, la definición de grano que el módulo 1 enseñó a declarar con tanto cuidado.

Esperar que validate_gold_schema() valide también los datos, no solo el esquema. Qué pasa: alguien espera que la función de este módulo detecte, además de columnas faltantes o de tipo incorrecto, problemas como filas duplicadas, valores nulos inesperados o revenue negativo. Por qué pasa: "validar" es una palabra amplia, y los módulos anteriores ya construyeron varias formas de validación de datos (validate_orders() en foundations, el assert de grano del módulo 1). Cómo detectarlo: si esperas que validate_gold_schema() reciba filas de datos como argumento, o que reporte algo como "hay 3 filas con revenue negativo", vas a encontrar una función mucho más estrecha de lo esperado. Cómo corregirlo: como el nombre lo indica con precisión, validate_gold_schema() valida esquema —nombres de columna y tipos—, no contenido. Es exactamente el límite que declara el diseño de esta guía: el "contrato" de este módulo es sobre la forma de una tabla, no sobre sus valores — la validación de datos completa, publicada como un sistema, es terreno de data-reliability-and-governance-guide.

Ejercicios

Ejercicio 1 — Clasifica una tabla hipotética nueva de Kiosko. Si Kiosko agregara mañana una tabla fact_inventory_snapshot —una fila por producto, por tienda, al cierre de cada día, con la cantidad de unidades en existencia— ¿qué tipo de hecho sería, de los tres que ya viste en el inventario de esta lección (transaccional, accumulating snapshot, cumulative)? Justifica en 2-3 frases.

Ver solución

Ninguno de los tres exactamente — sería un cuarto tipo, el periodic snapshot fact table que el proyecto del módulo 6 ya nombró sin construir: una fila por período fijo (el cierre de cada día), que describe un estado en ese instante (cuántas unidades hay), no un proceso que avanza (como fact_sessions) ni un resumen que se construye día a día sobre sí mismo con arreglos (como fact_store_activity). La pista más clara: cada fila de un periodic snapshot es independiente de la fila del día anterior — el inventario de hoy no se calcula "sumando" el de ayer, se mide de nuevo cada día — a diferencia de fact_store_activity, donde revenue_array_7d de hoy sí depende directamente del arreglo de ayer.

Ejercicio 2 — Cuenta cuántas tablas del inventario de esta lección usan una llave sustituta como llave primaria. Usando KIOSKO_DOMAIN, sin volver a mirar el código completo, identifica de memoria qué tablas usan una llave sustituta (_key) como identificador principal y cuáles no.

Ver solución
surrogate_key_tables = [t for t, kind, origin, rows, keys in KIOSKO_DOMAIN if "PK sustituta" in keys or "llave inteligente" in keys]
print(surrogate_key_tables)

Salida esperada:

['dim_store', 'dim_product', 'dim_product_scd', 'dim_date']

Las cuatro dimensiones usan llave sustituta —store_key, product_key (dos veces, en su versión simple y en su versión historizada), date_key—, mientras que las tres tablas de hechos no tienen una sola llave sustituta propia: fact_orders se identifica por su grano (order_id + product_id), fact_sessions por session_id (una llave natural que ya trae el clickstream), y fact_store_activity por la combinación store_id + activity_date. Este patrón —dimensiones con llave sustituta, hechos identificados por su grano— es consistente en las siete tablas del dominio de Kiosko.

Ejercicio 3 — Explica, de memoria, por qué fact_orders (40 filas) y fact_sessions (17 filas) no se pueden unir directamente por ninguna llave común sin perder o duplicar información. En 2-3 frases, explica qué llave tendrían que compartir estas dos tablas para unirse de forma limpia, y por qué esa llave no existe hoy en el dominio de Kiosko.

Ver solución

fact_orders tiene el grano de "una línea de orden" (identificada por order_id + product_id) y fact_sessions tiene el grano de "una sesión completa" (identificada por session_id) — no existe, en el dominio actual de Kiosko, ninguna columna que conecte una orden específica con la sesión de navegación que la originó (algo como un session_id dentro de fact_orders, o un order_id dentro de fact_sessions). Sin esa llave compartida, cualquier intento de unir las dos tablas —por ejemplo, por store_id y fecha— produciría un JOIN de muchos-a-muchos que multiplica filas sin ningún significado de negocio real, exactamente el tipo de error que el módulo 1 enseñó a detectar comparando COUNT(*) contra COUNT(DISTINCT ...).

Resumen y siguiente paso

Este módulo abre con un inventario, no con código nuevo: siete tablas —tres hechos, cuatro dimensiones— que los módulos 1 a 6 ya construyeron y verificaron, cada una con el tipo exacto que su proceso de negocio necesitaba. A eso se le llama, con precisión técnica y sin ninguna connotación negativa, un dominio feo: varios procesos de negocio conviviendo, compartiendo dimensiones conformadas, sin caber en el diagrama de una sola estrella. Este módulo agrega dos piezas que ese dominio todavía no tiene: una dimensión junk que agrupa atributos de baja cardinalidad en vez de multiplicar columnas sueltas, y un contrato formal —validate_gold_schema()— que confirma, con evidencia ejecutada, que las cuatro tablas gold de la guía mantienen el esquema que el resto del warehouse espera.

Antes de avanzar deberías poder: nombrar las siete tablas del dominio actual de Kiosko y su tipo exacto; explicar por qué "dominio feo" describe convivencia de procesos, no mal diseño; y anticipar las dos piezas nuevas que este módulo va a construir (dimensión junk, contrato de esquema).

La lección 2 profundiza el argumento central: por qué, en un dominio real, "un hecho y dos dimensiones" deja de ser suficiente en cuanto el negocio agrega un segundo proceso que medir — con el inventario de esta lección como evidencia de que eso ya le pasó a Kiosko.

Recursos