Saltar a contenido

Normalizer

1. Nombre del Patrón

  • Nombre oficial: Normalizer
  • Categoría: Message Transformation (Transformación de Mensajes)
  • Traducción contextual: Normalizador

2. Resumen Ejecutivo

Normalizer es el patrón que procesa mensajes de diferentes fuentes — cada una con su propio formato, estructura y convenciones — y los transforma en un formato común unificado. Cuando múltiples productores envían datos semánticamente equivalentes pero sintácticamente diferentes, el Normalizer actúa como punto de convergencia que elimina la variedad de formatos y produce un flujo homogéneo que los consumidores pueden procesar sin conocer el formato original de cada fuente.

El problema que resuelve es la heterogeneidad de formatos en un mundo donde los datos provienen de múltiples fuentes que no coordinaron sus formatos: proveedores que envían catálogos en CSV, XML y JSON; sistemas legacy que producen archivos de texto plano de ancho fijo; APIs modernas que retornan JSON con convenciones de naming diferentes; y feeds de terceros con estructuras completamente diferentes para datos semánticamente equivalentes.

En el contexto actual, Normalizer es más relevante que nunca por la proliferación de integraciones: las empresas modernas integran decenas o cientos de fuentes de datos (SaaS APIs, IoT devices, partner feeds, legacy systems). Sin normalización, cada consumidor debe implementar parsers para cada formato de cada fuente, multiplicando la complejidad. El Normalizer centraliza la complejidad de la heterogeneidad en un punto gobernado y produce un flujo normalizado que todos los consumidores comparten.


3. Definición Detallada

Propósito

Normalizer recibe mensajes de diferentes fuentes, identifica el formato de cada mensaje, aplica la transformación apropiada para ese formato, y produce un mensaje en el formato canónico común. Es la combinación de un Content-Based Router (para identificar el formato) con múltiples Message Translators (uno por formato de entrada) convergiendo en una única salida.

Lógica Arquitectónica

En sistemas de integración, la realidad es que las fuentes de datos no coordinan sus formatos. Un retailer que recibe catálogos de productos de 50 proveedores recibe 50 formatos diferentes:

  • Proveedor A: CSV con columnas product_name,price,sku_code.
  • Proveedor B: XML con elementos <Item><Description/><UnitCost/><ArticleNumber/></Item>.
  • Proveedor C: JSON con campos {"productTitle": ..., "retailPrice": ..., "supplierSKU": ...}.
  • Proveedor D: archivo de texto plano con campos de ancho fijo (posiciones 1-30: nombre, 31-40: precio).

Los datos son semánticamente equivalentes (nombre del producto, precio, SKU) pero sintácticamente incompatibles. Sin Normalizer, cada sistema consumidor (catálogo online, sistema de precios, sistema de inventario) debe implementar parsers para los 50 formatos, resultando en 50 × N consumidores = 50N integraciones.

Normalizer resuelve esto posicionándose como punto de convergencia:

  1. Detectar el formato: ¿es CSV, XML, JSON, texto plano? ¿De qué proveedor es?
  2. Seleccionar el translator: aplicar la transformación específica para ese formato/proveedor.
  3. Producir formato canónico: generar un mensaje en el formato interno estándar que todos los consumidores entienden.

Principio de Diseño Subyacente

El principio es absorber la variedad en la frontera del sistema: la heterogeneidad de las fuentes externas se resuelve una sola vez, en el punto de entrada, produciendo un flujo interno homogéneo. Los consumidores internos solo necesitan entender un formato — el formato canónico — independientemente de cuántas fuentes externas existan.

Problema Estructural que Resuelve

Sin Normalizer:

  • Cada consumidor debe conocer y parsear todos los formatos de todas las fuentes.
  • Añadir un nuevo proveedor requiere actualizar todos los consumidores con el nuevo formato.
  • La lógica de transformación está dispersa en múltiples consumidores, sin gobernanza ni reutilización.
  • Los errores de parsing se descubren en diferentes consumidores en diferentes momentos, dificultando el diagnóstico.

Contexto en el que Emerge

Normalizer emerge cuando:

  • Múltiples fuentes envían datos semánticamente equivalentes en formatos diferentes.
  • Los consumidores no deberían conocer ni manejar la diversidad de formatos.
  • Se necesita un formato interno estándar para procesamiento uniforme.
  • Las fuentes externas no pueden (o no quieren) cambiar su formato.

Por Qué No Es Trivial

  • Detección de formato: ¿cómo se identifica el formato del mensaje? ¿Por header? ¿Por el canal de entrada? ¿Por inspección del contenido?
  • Mapping semántico: los mismos datos tienen nombres diferentes en cada fuente. price, UnitCost, retailPrice, precio_unitario son el mismo concepto pero con diferentes nombres.
  • Tipos de datos: una fuente envía precios como strings ("12.50"), otra como números (12.5), otra con moneda incluida ("12,50 EUR"). La normalización debe homogeneizar los tipos.
  • Datos faltantes: una fuente incluye weight y otra no. ¿El formato canónico permite weight: null? ¿O se rechaza el mensaje?
  • Evolución de formatos: cuando un proveedor cambia su formato (añade un campo, renombra otro), el translator correspondiente debe actualizarse sin afectar a los demás.

Relación con Sistemas Distribuidos y Mensajería

Normalizer implementa el patrón Anti-Corruption Layer de Domain-Driven Design aplicado a la mensajería. El Anti-Corruption Layer protege el modelo de dominio interno de las idiosincrasias de los sistemas externos. El Normalizer es su implementación concreta en el contexto de integración por mensajes: transforma los formatos externos al modelo canónico interno.


4. Problema que Resuelve

El Problema Antes del Patrón

Un retailer de moda online opera en 5 países y recibe catálogos de productos de 30 proveedores. Cada proveedor envía su catálogo en un formato diferente:

Proveedor A (España, CSV):

codigo_articulo;nombre;precio_euros;talla;color;stock
ESP-NIKE-001;Zapatillas Air Max 90;129.99;42;Negro;150
ESP-NIKE-002;Camiseta Dri-FIT;34.99;M;Blanco;300

Proveedor B (Italia, XML):

<Catalogo>
  <Articolo>
    <CodiceFornitore>IT-GUCCI-2026-A1</CodiceFornitore>
    <Descrizione>Scarpe Mocassino</Descrizione>
    <Prezzo valuta="EUR">450.00</Prezzo>
    <Taglie>
      <Taglia>40</Taglia><Taglia>41</Taglia><Taglia>42</Taglia>
    </Taglie>
    <Colore>Nero</Colore>
    <Disponibilita>85</Disponibilita>
  </Articolo>
</Catalogo>

Proveedor C (UK, JSON via API):

{
  "products": [
    {
      "supplier_ref": "UK-BURBERRY-7891",
      "product_name": "Trench Coat Heritage",
      "price": { "amount": 1890, "currency": "GBP" },
      "sizes": ["S", "M", "L", "XL"],
      "colour": "Honey",
      "available_qty": 42
    }
  ]
}

Proveedor D (Alemania, archivo de ancho fijo):

DE-ADIDAS-3310    Ultraboost 22            00014999EUR44    Schwarz   00200
DE-ADIDAS-3311    Tiro Track Pants         00005999EUR L    Schwarz   00450

Síntomas del Problema

  • El equipo de catálogo mantiene 30 parsers diferentes, uno por proveedor. Cada parser fue escrito por un desarrollador diferente en un momento diferente.
  • Cuando el proveedor B cambió de Prezzo a PrezzoVendita en su XML, el parser se rompió y 200 productos desaparecieron del catálogo durante 6 horas.
  • El sistema de pricing tiene sus propios parsers (duplicados) porque se desarrolló después del catálogo y no reutilizó los parsers existentes.
  • Los precios en GBP del proveedor C no se convirtieron a EUR en el parser del catálogo pero sí en el parser de pricing, causando una discrepancia de precios visible para el cliente.
  • Añadir un nuevo proveedor (Proveedor E, Japón, formato JSON diferente) requiere modificar el catálogo, pricing y el sistema de inventario — tres equipos, tres sprints.
  • Los nombres de colores están en el idioma de cada proveedor: "Negro", "Nero", "Schwarz", "Black". El sistema de filtrado por color no funciona correctamente.

Impacto Operativo y Arquitectónico

  • 30 parsers × 3 consumidores = 90 integraciones de formato, cada una mantenida independientemente.
  • Inconsistencias de datos: el mismo producto tiene precios diferentes en catálogo y pricing por discrepancias en la conversión de moneda.
  • Fragilidad ante cambios: un cambio de formato de un proveedor puede romper múltiples consumidores.
  • Time-to-market lento: integrar un nuevo proveedor tarda semanas porque múltiples equipos deben implementar el parser.

Riesgos Si No Se Implementa Correctamente

  • Normalización incompleta: normalizar la estructura (JSON) pero no los valores (colores en diferentes idiomas, precios en diferentes monedas).
  • Normalizer monolítico: un solo componente que conoce los 30 formatos, difícil de mantener y probar.
  • Pérdida de datos: un translator que no mapea un campo del proveedor, perdiendo información silenciosamente.

Ejemplos Reales

  • Retail: normalización de catálogos de múltiples proveedores.
  • Healthcare: normalización de mensajes HL7v2, HL7 FHIR, CDA de diferentes hospitales al formato interno del HIE.
  • Finanzas: normalización de feeds de mercado de Bloomberg, Reuters, exchanges en formato FIX.
  • IoT: normalización de datos de sensores de diferentes fabricantes con diferentes formatos de telemetría.

5. Contexto de Aplicación

Cuándo Usarlo

  • Cuando múltiples fuentes envían datos semánticamente equivalentes en formatos diferentes.
  • Cuando los consumidores deben procesar datos de todas las fuentes de forma uniforme.
  • Cuando las fuentes externas no pueden cambiar su formato para adaptarse al sistema interno.
  • Cuando la lógica de transformación debe centralizarse para gobernabilidad y reutilización.

Cuándo No Usarlo

  • Cuando todas las fuentes ya usan el mismo formato (no hay heterogeneidad que normalizar).
  • Cuando cada fuente tiene consumidores dedicados que entienden su formato específico (no hay beneficio en un formato común).
  • Cuando la transformación es tan simple que un Message Translator inline en el consumidor es suficiente.

Precondiciones

  • Existe (o se puede definir) un formato canónico interno para los datos normalizados.
  • Existe un mecanismo para identificar el formato de cada mensaje entrante (canal, header, inspección de contenido).
  • Las diferencias entre formatos son mapeables: los datos de cada fuente pueden transformarse al formato canónico sin pérdida de información esencial.

Restricciones

  • Cada formato de entrada requiere su propio translator. Con N proveedores, hay N translators que mantener.
  • El formato canónico debe ser lo suficientemente expresivo para representar todos los datos de todas las fuentes.
  • La normalización puede perder información específica de una fuente que no tiene equivalente en el formato canónico.

Dependencias

  • Definición del formato canónico (schema).
  • Mecanismo de detección de formato (router o identificador).
  • Translators específicos para cada formato de entrada.
  • Governance que mantenga los translators actualizados ante cambios de formato de los proveedores.

Supuestos Arquitectónicos

  • Los datos de todas las fuentes son semánticamente equivalentes (representan el mismo concepto de negocio).
  • El formato canónico puede representar todos los datos esenciales de todas las fuentes.
  • Las fuentes no cambiarán para adaptarse al sistema interno; el sistema interno se adapta a las fuentes.

Tipo de Sistemas Donde Aparece con Más Frecuencia

  • Plataformas de e-commerce con múltiples proveedores.
  • Health Information Exchanges con múltiples hospitales.
  • Plataformas de trading con múltiples feeds de mercado.
  • IoT platforms con múltiples fabricantes de dispositivos.
  • Data lakes que ingestan datos de múltiples fuentes heterogéneas.

6. Fuerzas Arquitectónicas

Variedad de Fuentes vs. Uniformidad Interna

Más fuentes significa más formatos que normalizar, pero el beneficio de la uniformidad interna crece con el número de consumidores. Con 30 fuentes y 5 consumidores, el normalizer elimina 30×5 = 150 integraciones de formato, reemplazándolas con 30 translators + 5 consumidores que entienden un formato.

Fidelidad de la Transformación vs. Simplicidad del Formato Canónico

Un formato canónico complejo que capture todas las sutilezas de cada fuente es difícil de mantener pero no pierde información. Un formato canónico simple es fácil de usar pero puede no representar datos específicos de ciertas fuentes.

Centralización vs. Proximidad a la Fuente

¿Un normalizer centralizado que maneja todos los formatos, o normalizers distribuidos cerca de cada fuente? El centralizado es más fácil de gobernar pero es un monolito. Los distribuidos son más resilientes pero más difíciles de coordinar.

Detección Automática vs. Explícita

¿Detectar el formato automáticamente (inspección de contenido) o explícitamente (canal dedicado por fuente, header de formato)? La detección automática es más flexible pero propensa a errores. La detección explícita es más confiable pero requiere configuración por fuente.

Normalización Completa vs. Incremental

¿Normalizar todos los campos del mensaje o solo los campos que los consumidores necesitan actualmente? La normalización completa es más robusta pero más costosa de implementar. La normalización incremental es más ágil pero puede requerir actualizaciones cuando un consumidor necesita un campo que no se había normalizado.


7. Estructura Conceptual del Patrón

Actores o Componentes Involucrados

  1. Fuentes Externas: sistemas que producen mensajes en formatos heterogéneos.
  2. Format Detector / Router: identifica el formato del mensaje entrante y lo enruta al translator apropiado.
  3. Message Translators: uno por cada formato de entrada, transforma al formato canónico.
  4. Formato Canónico: el schema común al que se normalizan todos los mensajes.
  5. Canal de Salida: el canal donde se publican los mensajes normalizados.
  6. Consumidores: sistemas que procesan los mensajes normalizados sin conocer la fuente original.

Flujo Lógico

flowchart TD
    A([Fuente A\nCSV español]) --> D[Format Detector]
    B([Fuente B\nXML italiano]) --> D
    C([Fuente C\nJSON inglés]) --> D
    D --> E{Formato\ndetectado?}
    E -- csv-proveedor-espana --> F[CSV Translator]
    E -- xml-proveedor-italia --> G[XML Translator]
    E -- json-proveedor-uk --> H[JSON Translator]
    F --> I[Formato canónico\nsku, name, price, stock...]
    G --> I
    H --> I
    I --> J[(Topic: catalog.products.normalized)]
    J --> K([Consumidores procesan\nsin conocer la fuente])

Responsabilidades

Componente Responsabilidad
Fuentes externas Enviar datos en su formato propio
Format Detector Identificar el formato/fuente del mensaje
Translators Transformar del formato específico al canónico
Formato Canónico Definir el contrato común para todos los datos normalizados
Consumidores Procesar mensajes normalizados sin conocer la fuente

Interacciones

  • Fuente → Canal de entrada: cada fuente publica en su canal o en un canal compartido.
  • Canal → Format Detector: el detector identifica el formato.
  • Format Detector → Translator: enruta al translator correcto.
  • Translator → Canal de salida: el translator publica el mensaje normalizado.

Contratos Implícitos

  • Completitud: el mensaje normalizado contiene al menos los campos requeridos del formato canónico.
  • Fidelidad: los datos no se pierden ni se alteran en la transformación (excepto conversiones de formato).
  • Consistencia: el mismo dato en fuentes diferentes produce el mismo valor en el formato canónico.

Decisiones de Diseño Clave

  1. Detección de formato: ¿por canal (cada fuente publica en su propio topic), por header (metadata del mensaje), o por inspección (análisis del contenido)?
  2. Translator por fuente vs. por formato: ¿un translator por proveedor (más específico) o uno por tipo de formato (CSV, XML, JSON) con configuración por proveedor?
  3. Normalización de valores: ¿normalizar solo la estructura o también los valores (colores, monedas, unidades de medida)?
  4. Datos no mapeables: ¿descartar datos que no tienen equivalente en el formato canónico, o preservarlos en un campo "extras"?
  5. Validación post-normalización: ¿validar que el mensaje normalizado conforma al schema canónico antes de publicar?

8. Ejemplo Arquitectónico Detallado

Dominio: Retail — Normalización de Catálogos de Múltiples Proveedores

Contexto del Negocio

Un retailer de moda online recibe catálogos de productos de 30 proveedores internacionales. Cada proveedor envía sus datos en un formato diferente (CSV, XML, JSON, texto plano). Los datos incluyen nombre del producto, precio (en diferentes monedas), tallas (en diferentes sistemas), colores (en diferentes idiomas) y stock. El retailer necesita un catálogo unificado para su tienda online, sistema de pricing y gestión de inventario.

Necesidad de Integración

Normalizar los catálogos de todos los proveedores a un formato canónico interno para que los sistemas consumidores (catálogo online, pricing engine, inventory management) procesen datos homogéneos independientemente del proveedor de origen.

Sistemas Involucrados

  1. Supplier Feeds: 30 proveedores con diferentes formatos y canales (SFTP, API, email, EDI).
  2. Ingestion Adapters: Channel Adapters que reciben los feeds y los publican en Kafka.
  3. Format Detector: identifica el proveedor/formato de cada mensaje.
  4. Supplier Translators: 30 translators, uno por proveedor.
  5. Value Normalizer: normaliza colores, monedas, tallas a valores internos.
  6. Schema Validator: valida que el mensaje normalizado conforma al schema canónico.
  7. Kafka: backbone de mensajería.
  8. Consumidores: Catalog Service, Pricing Engine, Inventory Service.

Restricciones Técnicas

  • Los proveedores no cambiarán su formato. El retailer se adapta a ellos.
  • Nuevos proveedores se integran cada trimestre (3-5 nuevos proveedores por año).
  • Los precios deben convertirse a EUR usando la tasa de cambio del día.
  • Las tallas deben mapearse al sistema EU (36-50 para calzado, XS-XXL para ropa).
  • Los colores deben mapearse a un catálogo interno de 48 colores estándar.

Diseño del Normalizer

Formato canónico (schema del producto normalizado):

{
  "$schema": "https://catalog.retailer.com/schemas/product/v3",
  "sku": "string (formato interno: PROV-ORIG-SKU)",
  "supplier_code": "string",
  "name": "string (normalizado a español)",
  "price": {
    "amount_eur": "number (convertido a EUR)",
    "original_amount": "number",
    "original_currency": "string (ISO 4217)"
  },
  "sizes": ["string (sistema EU)"],
  "color": {
    "code": "string (código interno: COL-001 a COL-048)",
    "name_es": "string (nombre en español)"
  },
  "stock_quantity": "integer",
  "source": {
    "supplier": "string",
    "original_ref": "string",
    "normalized_at": "datetime"
  }
}

Ejemplo de normalización del proveedor UK (GBP, UK sizes):

Entrada:

{
  "supplier_ref": "UK-BURBERRY-7891",
  "product_name": "Trench Coat Heritage",
  "price": { "amount": 1890, "currency": "GBP" },
  "sizes": ["S", "M", "L", "XL"],
  "colour": "Honey",
  "available_qty": 42
}

Salida normalizada:

{
  "sku": "BURB-UK-BURBERRY-7891",
  "supplier_code": "BURBERRY-UK",
  "name": "Trench Coat Heritage",
  "price": {
    "amount_eur": 2192.40,
    "original_amount": 1890,
    "original_currency": "GBP"
  },
  "sizes": ["S", "M", "L", "XL"],
  "color": {
    "code": "COL-012",
    "name_es": "Miel"
  },
  "stock_quantity": 42,
  "source": {
    "supplier": "BURBERRY-UK",
    "original_ref": "UK-BURBERRY-7891",
    "normalized_at": "2026-04-07T10:15:00Z"
  }
}

Decisiones Arquitectónicas

  1. Un translator por proveedor: cada proveedor tiene su propio translator (microservicio o función) que conoce el formato exacto de ese proveedor. Los translators comparten una librería común de normalización de valores.
  2. Detección por canal de entrada: cada proveedor publica en su propio topic (por ejemplo, supplier.burberry-uk.raw). No se necesita detección de formato — el canal identifica el proveedor.
  3. Normalización de valores centralizada: una librería compartida normaliza colores (mapping table), monedas (API de tasas de cambio) y tallas (mapping table por categoría).
  4. Preservación del valor original: el formato canónico preserva el precio original y la moneda original junto con el precio normalizado a EUR. Esto permite auditoría y reconciliación.
  5. Validación post-normalización: cada mensaje normalizado se valida contra el JSON Schema canónico antes de publicarse. Los mensajes que no validan van a dead-letter con el error de validación.

Riesgos y Mitigaciones

Riesgo Mitigación
Proveedor cambia su formato sin avisar Translator falla y envía a dead-letter; alerta al equipo
Tasa de cambio incorrecta Usar API confiable (ECB, Bloomberg); cachear con TTL de 1 hora
Color no reconocido (no está en el mapping) Default a "COL-000" (Sin clasificar) + alerta para actualizar mapping
Translator incorrecto que mapea campos mal Testing automatizado por proveedor con datos de referencia

9. Desarrollo Paso a Paso del Ejemplo

Paso 1: Ingestion del Feed del Proveedor

El proveedor Burberry (UK) publica su catálogo actualizado via API REST. El Ingestion Adapter para Burberry (un scheduled job que ejecuta cada hora) llama a la API del proveedor, obtiene los productos actualizados, y publica cada producto como un mensaje individual en el topic Kafka supplier.burberry-uk.raw.

Paso 2: Format Detection (Implícita por Canal)

El Normalizer Service está suscrito a todos los topics supplier.*.raw. Cuando recibe un mensaje de supplier.burberry-uk.raw, sabe que es del proveedor Burberry UK y selecciona el translator correspondiente: BurberryUKTranslator.

Paso 3: Structural Transformation

El BurberryUKTranslator mapea la estructura del JSON del proveedor al formato canónico:

  • supplier_refsku (con prefijo BURB-)
  • product_namename
  • price.amountprice.original_amount
  • price.currencyprice.original_currency
  • sizessizes (ya es un array, pero necesita normalización de valores)
  • colourcolor (necesita normalización de valores)
  • available_qtystock_quantity

Paso 4: Value Normalization

Después de la transformación estructural, el Value Normalizer procesa los valores:

Conversión de moneda: 1890 GBP × 1.16 (tasa del día) = 2192.40 EUR.

Normalización de color: el color "Honey" no es un color estándar. El mapping table busca: - "Honey" → encontrado como alias de "Miel" → código COL-012. - Si no se encontrara, se asignaría COL-000 (Sin clasificar) y se generaría una alerta.

Normalización de tallas: para la categoría "outerwear", las tallas S/M/L/XL son universales y no requieren conversión. Si fueran tallas UK de calzado (UK 7 → EU 40.5), el mapping las convertiría.

Paso 5: Validación y Publicación

El mensaje normalizado se valida contra el JSON Schema canónico v3: - ¿Tiene todos los campos requeridos? Sí. - ¿Los tipos son correctos? Sí (amount_eur es number, stock_quantity es integer). - ¿El código de color es válido? Sí (COL-012 existe en el catálogo).

Validación exitosa. El mensaje normalizado se publica en catalog.products.normalized.

Paso 6: Consumo Uniforme

Los consumidores procesan mensajes de catalog.products.normalized sin saber que vino de Burberry UK:

  • Catalog Service: actualiza la ficha del producto en la tienda online.
  • Pricing Engine: actualiza el precio (2192.40 EUR) aplicando su regla de markup.
  • Inventory Service: actualiza el stock disponible (42 unidades).

Todos procesan el mismo formato canónico, independientemente de que el producto haya venido de Burberry (JSON/GBP) o de un proveedor español (CSV/EUR).


10. Diagrama Técnico del Patrón

Código Python con diagrams

Diagrama General

Diagrama AWS

Diagrama Azure

Ver / Copiar código de los diagramas
from diagrams import Diagram, Cluster, Edge
from diagrams.onprem.queue import Kafka
from diagrams.onprem.compute import Server
from diagrams.generic.storage import Storage
from diagrams.onprem.database import PostgreSQL

with Diagram("Normalizer - Retail Multi-Supplier Product Catalog", show=False, direction="LR"):

    with Cluster("Proveedores (formatos heterogéneos)"):
        supplier_a = Server("Proveedor A\n(CSV/EUR)")
        supplier_b = Server("Proveedor B\n(XML/EUR)")
        supplier_c = Server("Proveedor C\n(JSON/GBP)")
        supplier_d = Server("Proveedor D\n(Fixed-Width/EUR)")

    with Cluster("Ingestion (topics por proveedor)"):
        t_a = Kafka("supplier.prov-a.raw")
        t_b = Kafka("supplier.prov-b.raw")
        t_c = Kafka("supplier.prov-c.raw")
        t_d = Kafka("supplier.prov-d.raw")

    with Cluster("Normalizer"):
        router = Server("Format\nDetector\n& Router")

        with Cluster("Translators"):
            trans_a = Server("CSV\nTranslator A")
            trans_b = Server("XML\nTranslator B")
            trans_c = Server("JSON\nTranslator C")
            trans_d = Server("Fixed-Width\nTranslator D")

        value_norm = Server("Value\nNormalizer\n(color, currency,\nsizes)")
        validator = Server("Schema\nValidator")

    with Cluster("Datos de Referencia"):
        color_map = Storage("Color\nMapping")
        fx_rates = Storage("FX Rates\n(EUR)")
        size_map = Storage("Size\nMapping")

    with Cluster("Salida normalizada"):
        t_normalized = Kafka("catalog.products\n.normalized")

    with Cluster("Consumidores"):
        catalog = Server("Catalog\nService")
        pricing = Server("Pricing\nEngine")
        inventory = Server("Inventory\nService")

    # Ingestion
    supplier_a >> t_a
    supplier_b >> t_b
    supplier_c >> t_c
    supplier_d >> t_d

    # Routing
    t_a >> router >> trans_a
    t_b >> router >> trans_b
    t_c >> router >> trans_c
    t_d >> router >> trans_d

    # Value normalization
    trans_a >> value_norm
    trans_b >> value_norm
    trans_c >> value_norm
    trans_d >> value_norm

    value_norm >> Edge(style="dashed") >> color_map
    value_norm >> Edge(style="dashed") >> fx_rates
    value_norm >> Edge(style="dashed") >> size_map

    # Validation & output
    value_norm >> validator >> t_normalized

    # Consumption
    t_normalized >> catalog
    t_normalized >> pricing
    t_normalized >> inventory
from diagrams import Diagram, Cluster, Edge
from diagrams.aws.compute import Lambda, ECS
from diagrams.aws.database import Dynamodb
from diagrams.aws.integration import SQS
from diagrams.aws.analytics import GlueDataCatalog


with Diagram("Normalizer - Retail Multi-Supplier Product Catalog (AWS)", show=False, direction="LR"):

    with Cluster("Proveedores (formatos heterogéneos)"):
        supplier_a = ECS("Proveedor A\n(CSV/EUR)")
        supplier_b = ECS("Proveedor B\n(XML/EUR)")
        supplier_c = ECS("Proveedor C\n(JSON/GBP)")
        supplier_d = ECS("Proveedor D\n(Fixed-Width/EUR)")

    with Cluster("Ingestion (SQS por proveedor)"):
        t_a = SQS("supplier.prov-a.raw")
        t_b = SQS("supplier.prov-b.raw")
        t_c = SQS("supplier.prov-c.raw")
        t_d = SQS("supplier.prov-d.raw")

    with Cluster("Normalizer (Lambda)"):
        router = Lambda("Format\nDetector\n& Router")

        with Cluster("Translators"):
            trans_a = Lambda("CSV\nTranslator A")
            trans_b = Lambda("XML\nTranslator B")
            trans_c = Lambda("JSON\nTranslator C")
            trans_d = Lambda("Fixed-Width\nTranslator D")

        value_norm = Lambda("Value\nNormalizer\n(color, currency,\nsizes)")
        validator = Lambda("Schema\nValidator")

    with Cluster("Datos de Referencia"):
        color_map = Dynamodb("Dynamodb\nColor Mapping")
        fx_rates = Dynamodb("Dynamodb\nFX Rates (EUR)")
        size_map = Dynamodb("Dynamodb\nSize Mapping")

    with Cluster("Salida normalizada"):
        t_normalized = SQS("catalog.products\n.normalized")
        schema_reg = GlueDataCatalog("Glue Schema\nRegistry")

    with Cluster("Consumidores"):
        catalog = Lambda("Catalog\nService")
        pricing = Lambda("Pricing\nEngine")
        inventory = Lambda("Inventory\nService")

    # Ingestion
    supplier_a >> t_a
    supplier_b >> t_b
    supplier_c >> t_c
    supplier_d >> t_d

    # Routing
    t_a >> router >> trans_a
    t_b >> router >> trans_b
    t_c >> router >> trans_c
    t_d >> router >> trans_d

    # Value normalization
    trans_a >> value_norm
    trans_b >> value_norm
    trans_c >> value_norm
    trans_d >> value_norm

    value_norm >> Edge(style="dashed") >> color_map
    value_norm >> Edge(style="dashed") >> fx_rates
    value_norm >> Edge(style="dashed") >> size_map

    # Validation & output
    value_norm >> validator >> t_normalized
    validator >> Edge(style="dashed") >> schema_reg

    # Consumption
    t_normalized >> catalog
    t_normalized >> pricing
    t_normalized >> inventory
from diagrams import Diagram, Cluster, Edge
from diagrams.azure.compute import FunctionApps
from diagrams.azure.database import DatabaseForPostgresqlServers
from diagrams.azure.integration import ServiceBus
from diagrams.azure.storage import BlobStorage


with Diagram("Normalizer - Retail Multi-Supplier Product Catalog (Azure)", show=False, direction="LR"):

    with Cluster("Proveedores (formatos heterogéneos)"):
        supplier_a = FunctionApps("Proveedor A\n(CSV/EUR)")
        supplier_b = FunctionApps("Proveedor B\n(XML/EUR)")
        supplier_c = FunctionApps("Proveedor C\n(JSON/GBP)")
        supplier_d = FunctionApps("Proveedor D\n(Fixed-Width/EUR)")

    with Cluster("Ingestion (topics por proveedor)"):
        t_a = ServiceBus("supplier.prov-a.raw")
        t_b = ServiceBus("supplier.prov-b.raw")
        t_c = ServiceBus("supplier.prov-c.raw")
        t_d = ServiceBus("supplier.prov-d.raw")

    with Cluster("Normalizer"):
        router = FunctionApps("Format\nDetector\n& Router")

        with Cluster("Translators"):
            trans_a = FunctionApps("CSV\nTranslator A")
            trans_b = FunctionApps("XML\nTranslator B")
            trans_c = FunctionApps("JSON\nTranslator C")
            trans_d = FunctionApps("Fixed-Width\nTranslator D")

        value_norm = FunctionApps("Value\nNormalizer\n(color, currency,\nsizes)")
        validator = FunctionApps("Schema\nValidator")

    with Cluster("Datos de Referencia"):
        color_map = BlobStorage("Color\nMapping")
        fx_rates = BlobStorage("FX Rates\n(EUR)")
        size_map = BlobStorage("Size\nMapping")

    with Cluster("Salida normalizada"):
        t_normalized = ServiceBus("catalog.products\n.normalized")

    with Cluster("Consumidores"):
        catalog = FunctionApps("Catalog\nService")
        pricing = FunctionApps("Pricing\nEngine")
        inventory = FunctionApps("Inventory\nService")

    # Ingestion
    supplier_a >> t_a
    supplier_b >> t_b
    supplier_c >> t_c
    supplier_d >> t_d

    # Routing
    t_a >> router >> trans_a
    t_b >> router >> trans_b
    t_c >> router >> trans_c
    t_d >> router >> trans_d

    # Value normalization
    trans_a >> value_norm
    trans_b >> value_norm
    trans_c >> value_norm
    trans_d >> value_norm

    value_norm >> Edge(style="dashed") >> color_map
    value_norm >> Edge(style="dashed") >> fx_rates
    value_norm >> Edge(style="dashed") >> size_map

    # Validation & output
    value_norm >> validator >> t_normalized

    # Consumption
    t_normalized >> catalog
    t_normalized >> pricing
    t_normalized >> inventory

Explicación del Diagrama

El diagrama muestra el flujo de normalización:

  1. Los proveedores envían datos en formatos heterogéneos (CSV, XML, JSON, Fixed-Width).
  2. Los Ingestion Adapters publican en topics específicos por proveedor.
  3. El Format Detector & Router identifica el proveedor y enruta al translator correcto.
  4. Los Translators transforman la estructura al formato canónico.
  5. El Value Normalizer normaliza colores, monedas y tallas usando datos de referencia.
  6. El Schema Validator verifica conformidad con el schema canónico.
  7. Los mensajes normalizados se publican en catalog.products.normalized.
  8. Los consumidores procesan mensajes homogéneos sin conocer la fuente original.

Correspondencia Patrón ↔ Diagrama

Concepto del Patrón Componente del Diagrama
Fuentes heterogéneas Proveedores A, B, C, D
Format Detector Format Detector & Router
Message Translators CSV, XML, JSON, Fixed-Width Translators
Value Normalization Value Normalizer + mapping tables
Formato canónico Schema validado, topic normalizado
Consumidores uniformes Catalog, Pricing, Inventory Services

11. Beneficios

Impacto Técnico

  • Reducción de integraciones de N×M a N+M: con N proveedores y M consumidores, se pasa de N×M parsers a N translators + M consumidores de un formato.
  • Single source of truth del formato: el formato canónico es la única definición que los consumidores necesitan conocer.
  • Aislamiento de cambios: un cambio de formato de un proveedor afecta solo a su translator, no a los consumidores.
  • Normalización de valores: no solo estructura, sino también semántica — colores, monedas, tallas unificados.

Impacto Organizacional

  • Onboarding de proveedores más rápido: integrar un nuevo proveedor requiere solo un nuevo translator, no modificar los consumidores.
  • Onboarding de consumidores más rápido: un nuevo consumidor solo necesita entender el formato canónico, no los 30 formatos de proveedores.
  • Ownership claro: el equipo de integración mantiene los translators; los equipos de dominio mantienen los consumidores.

Impacto Operacional

  • Debugging centralizado: los errores de parsing se detectan y registran en el Normalizer, no dispersos en múltiples consumidores.
  • Calidad de datos uniforme: la validación post-normalización garantiza que todos los mensajes cumplen el schema canónico.
  • Monitoreo por proveedor: métricas de éxito/fallo de normalización por proveedor permiten identificar proveedores problemáticos.

Beneficios de Mantenibilidad y Evolución

  • Evolución del formato canónico: si se añade un campo al schema canónico, se actualizan los translators que pueden proveer ese dato.
  • Testing por proveedor: cada translator se puede testear independientemente con datos de referencia del proveedor.
  • Configuración declarativa: los mappings de colores, monedas y tallas se definen en tablas de configuración, no en código.

12. Desventajas y Riesgos

Complejidad Añadida

  • N translators que mantener: con 30 proveedores, hay 30 translators que deben actualizarse cuando el proveedor cambia su formato.
  • Mapping tables: las tablas de mapeo de colores, tallas y monedas deben mantenerse actualizadas y completas.
  • Formato canónico como cuello de botella: si el formato canónico no es suficientemente expresivo, los translators pierden información.

Riesgos de Mal Uso

  • Normalización solo estructural: normalizar la estructura (JSON) sin normalizar los valores (colores en diferentes idiomas siguen diferentes). El consumidor sigue viendo inconsistencias.
  • Formato canónico demasiado genérico: un formato tan flexible que no obliga a nada (todos los campos opcionales, tipos laxos). Los consumidores reciben mensajes "normalizados" que siguen siendo heterogéneos.
  • Normalizer como God Service: añadir lógica de negocio al normalizer (cálculo de precios finales, reglas de descuento) en lugar de solo transformación de formato.

Sobreingeniería

  • Implementar un motor de transformación genérico configurable cuando translators simples por proveedor son suficientes.
  • Normalizar todos los campos de cada proveedor cuando los consumidores solo usan un subconjunto.
  • Implementar un sistema de detección automática de formato con ML cuando la detección por canal es suficiente.

Costos de Operación

  • Mantenimiento de translators: cada cambio de formato de un proveedor requiere actualización del translator correspondiente.
  • Mantenimiento de mapping tables: nuevos colores, nuevas monedas, nuevos sistemas de tallas deben añadirse.
  • Testing continuo: los datos de referencia por proveedor deben actualizarse periódicamente para reflejar cambios.

Anti-Patterns Relacionados

  • Leaky Normalization: el mensaje "normalizado" aún contiene artefactos del formato original (campo originalFormat: "xml" que los consumidores usan para branching).
  • Normalization to Nothing: el formato canónico es tan simple que pierde información importante. Los consumidores complementan con llamadas directas al proveedor.
  • Copy-Paste Translators: translators duplicados con variaciones mínimas que deberían ser un solo translator configurable.

13. Relación con Otros Patrones

Patrones Complementarios

  • Canonical Data Model (este capítulo): define el formato al que el Normalizer transforma. El Normalizer es la implementación práctica del Canonical Data Model.
  • Channel Adapter (Messaging Channels): los adapters de ingestion que reciben feeds de proveedores y los publican en Kafka son Channel Adapters.
  • Content-Based Router (Message Routing): el Format Detector dentro del Normalizer es un Content-Based Router que examina el formato y enruta al translator correcto.

Patrones que Suelen Aparecer Antes o Después

  • Antes: Channel Adapter recibe el feed del proveedor (SFTP, API, email) y lo publica en el canal de entrada del Normalizer.
  • Después: Content Filter puede eliminar campos no necesarios del mensaje normalizado para consumidores específicos.

Combinaciones Comunes

  • Normalizer + Content Enricher: después de normalizar la estructura, el enricher añade datos que el proveedor no provee (categoría interna, marca, temporada).
  • Normalizer + Dead Letter Channel: mensajes que no se pueden normalizar (formato inesperado, campos requeridos faltantes) van a dead-letter para revisión manual.
  • Normalizer + Wire Tap: capturar el mensaje original antes de normalizar para auditoría y debugging.

Diferencias con Patrones Similares

  • vs. Message Translator: Message Translator transforma un formato específico a otro. Normalizer es la composición de Router + múltiples Translators para manejar N formatos de entrada convergiendo a un formato de salida.
  • vs. Canonical Data Model: Canonical Data Model es el concepto del formato común. Normalizer es el mecanismo que lo implementa transformando mensajes heterogéneos al formato canónico.
  • vs. Content-Based Router: el router solo enruta; el Normalizer enruta Y transforma.

Encaje en un Flujo Mayor de Integración

El Normalizer se posiciona en la frontera del sistema, entre las fuentes externas y los sistemas internos. Es el primer paso del pipeline de procesamiento: ingest → normalize → enrich → route → consume. Todos los datos que entran al sistema pasan por el Normalizer antes de ser procesados.


14. Relevancia Actual del Patrón

Evaluación: Relevancia Alta

Argumentación

Normalizer tiene relevancia alta y creciente por la explosión del número de fuentes de datos en las organizaciones modernas:

  • SaaS proliferation: las empresas usan 50-200 aplicaciones SaaS, cada una con su propia API y formato de datos. Normalizar datos de Salesforce, HubSpot, Zendesk, Shopify, etc. al formato interno es esencial.
  • API economy: los partners comerciales exponen APIs con formatos diferentes. Las plataformas de marketplace integran datos de miles de vendedores.
  • IoT heterogeneity: una fábrica tiene sensores de 10 fabricantes diferentes, cada uno con su protocolo y formato de telemetría.
  • Data mesh: los data products de diferentes dominios exponen datos en formatos potencialmente diferentes. Los consumidores de datos cross-domain necesitan normalización.

Cómo Se Implementa Hoy

Tecnología Mecanismo de Normalización
Apache Kafka + Connect Kafka Connect SMT (Single Message Transforms) por connector
AWS Glue ETL jobs con DataBrew para normalización visual
dbt (Data Build Tool) SQL transformations para normalización en data warehouse
Apache Camel Normalizer EIP con Routing + multiple Translators
Mulesoft DataWeave transformations por fuente
Airbyte / Fivetran Connectors con normalización automática a schema estándar

Qué Parte Sigue Siendo Esencial

  • La absorción de heterogeneidad en la frontera: el principio de normalizar en el punto de entrada, no en los consumidores.
  • Normalización de valores además de estructura: colores, monedas, unidades, códigos deben homogeneizarse.
  • Validación post-normalización: garantizar que el mensaje normalizado cumple el schema canónico.
  • Observabilidad por fuente: monitorear tasa de éxito/fallo de normalización por fuente para detectar cambios de formato.

15. Implementación en Arquitecturas Modernas

Kafka Connect — SMT por Connector

{
  "name": "supplier-burberry-uk",
  "config": {
    "connector.class": "io.confluent.connect.http.HttpSourceConnector",
    "http.api.url": "https://api.burberry.com/catalog/v2/products",
    "transforms": "extractProduct,normalizePrice,normalizeColor",
    "transforms.extractProduct.type": "org.apache.kafka.connect.transforms.ExtractField$Value",
    "transforms.extractProduct.field": "products",
    "transforms.normalizePrice.type": "com.retailer.kafka.transforms.CurrencyNormalizer",
    "transforms.normalizePrice.source.currency.field": "price.currency",
    "transforms.normalizePrice.source.amount.field": "price.amount",
    "transforms.normalizePrice.target.currency": "EUR",
    "transforms.normalizeColor.type": "com.retailer.kafka.transforms.ColorNormalizer",
    "transforms.normalizeColor.source.field": "colour",
    "transforms.normalizeColor.target.field": "color.code",
    "topic": "catalog.products.normalized"
  }
}

Python — Normalizer Service con Strategy Pattern

from abc import ABC, abstractmethod

class SupplierTranslator(ABC):
    @abstractmethod
    def translate(self, raw_message: dict) -> dict:
        pass

class BurberryUKTranslator(SupplierTranslator):
    def translate(self, raw: dict) -> dict:
        return {
            "sku": f"BURB-{raw['supplier_ref']}",
            "supplier_code": "BURBERRY-UK",
            "name": raw["product_name"],
            "price": {
                "original_amount": raw["price"]["amount"],
                "original_currency": raw["price"]["currency"],
                "amount_eur": self.convert_currency(
                    raw["price"]["amount"], raw["price"]["currency"]
                )
            },
            "sizes": raw.get("sizes", []),
            "color": self.normalize_color(raw.get("colour", "Unknown")),
            "stock_quantity": raw.get("available_qty", 0),
            "source": {
                "supplier": "BURBERRY-UK",
                "original_ref": raw["supplier_ref"],
                "normalized_at": datetime.utcnow().isoformat()
            }
        }

class NormalizerService:
    def __init__(self):
        self.translators: dict[str, SupplierTranslator] = {
            "supplier.burberry-uk.raw": BurberryUKTranslator(),
            "supplier.nike-es.raw": NikeESTranslator(),
            "supplier.gucci-it.raw": GucciITTranslator(),
            # ... 30 translators
        }

    def normalize(self, topic: str, raw_message: dict) -> dict:
        translator = self.translators.get(topic)
        if not translator:
            raise UnknownSupplierError(f"No translator for topic: {topic}")

        normalized = translator.translate(raw_message)
        self.validate_schema(normalized)
        return normalized

Apache Camel — Normalizer EIP

from("kafka:supplier.*.raw")
    .choice()
        .when(header("kafka.TOPIC").contains("burberry-uk"))
            .bean(BurberryUKTranslator.class, "translate")
        .when(header("kafka.TOPIC").contains("nike-es"))
            .bean(NikeESTranslator.class, "translate")
        .when(header("kafka.TOPIC").contains("gucci-it"))
            .bean(GucciITTranslator.class, "translate")
    .end()
    .bean(ValueNormalizer.class, "normalizeValues")
    .bean(SchemaValidator.class, "validate")
    .to("kafka:catalog.products.normalized");

dbt — Normalización en Data Warehouse

-- models/normalized/products.sql
WITH raw_burberry AS (
    SELECT
        'BURB-' || supplier_ref AS sku,
        'BURBERRY-UK' AS supplier_code,
        product_name AS name,
        price_amount * {{ get_fx_rate('GBP', 'EUR') }} AS price_amount_eur,
        price_amount AS price_original_amount,
        'GBP' AS price_original_currency,
        sizes,
        {{ normalize_color('colour') }} AS color_code,
        available_qty AS stock_quantity
    FROM {{ source('suppliers', 'burberry_uk_raw') }}
),
raw_nike AS (
    SELECT
        'NIKE-' || codigo_articulo AS sku,
        'NIKE-ES' AS supplier_code,
        nombre AS name,
        precio_euros AS price_amount_eur,
        precio_euros AS price_original_amount,
        'EUR' AS price_original_currency,
        ARRAY[talla] AS sizes,
        {{ normalize_color('color') }} AS color_code,
        stock AS stock_quantity
    FROM {{ source('suppliers', 'nike_es_raw') }}
)
SELECT * FROM raw_burberry
UNION ALL
SELECT * FROM raw_nike
-- UNION ALL ... (30 proveedores)

16. Consideraciones de Gobierno y Operación

Observabilidad

  • Normalization success rate por proveedor: porcentaje de mensajes normalizados exitosamente vs. rechazados, por proveedor.
  • Unmapped values: colores, tallas o monedas que no se encontraron en las mapping tables (indicador de tablas desactualizadas).
  • Schema validation failures: mensajes que pasan la transformación pero no validan contra el schema canónico.
  • Translator latency: tiempo de transformación por proveedor, para detectar translators ineficientes.

Monitoreo

  • Dead-letter rate por proveedor: si un proveedor tiene alta tasa de dead-letter, probablemente cambió su formato.
  • Mapping table completeness: alertar cuando más del 5% de los valores de un campo no se pueden mapear.
  • FX rate freshness: alertar si la tasa de cambio no se ha actualizado en más de 24 horas.
  • Translator version: monitorear qué versión de cada translator está desplegada.

Versionado

  • Schema canónico: versionado con backward compatibility (nuevos campos opcionales). Los consumidores usan tolerant reader.
  • Translators: cada translator tiene su propio ciclo de vida. Un cambio de formato del proveedor requiere un nuevo release del translator.
  • Mapping tables: las tablas de mapeo tienen versionado y se actualizan sin necesidad de redesplegar el servicio.

Seguridad

  • Datos de proveedores: los feeds de proveedores pueden contener información comercial confidencial (precios de compra, stock real). Cifrado en tránsito y en reposo.
  • API credentials: las credenciales de las APIs de proveedores deben almacenarse en un vault (AWS Secrets Manager, HashiCorp Vault).
  • Access control: solo el equipo de integración tiene acceso a los topics raw; los consumidores acceden al topic normalizado.

Manejo de Errores

  • Formato inesperado: si el translator no puede parsear el mensaje, enviar a dead-letter con el error de parsing y alertar al equipo.
  • Valor no mapeable: si un color o talla no se encuentra en la mapping table, usar un valor default ("Sin clasificar") y alertar para actualización de la tabla.
  • FX rate unavailable: si la API de tasas de cambio no responde, usar la última tasa conocida (cacheada) y alertar.
  • Schema validation failure: si el mensaje normalizado no valida, enviar a dead-letter con el error de validación.

17. Errores Comunes

Normalizar Solo la Estructura, No los Valores

Transformar todos los mensajes a JSON pero dejar los colores en diferentes idiomas ("Negro", "Nero", "Black", "Schwarz"), los precios en diferentes monedas y las tallas en diferentes sistemas. Los consumidores reciben JSON "normalizado" pero con valores incomparables. La normalización de valores es tan importante como la normalización de estructura.

No Preservar los Valores Originales

Reemplazar el precio original (1890 GBP) con solo el precio convertido (2192.40 EUR) sin guardar el original. Cuando la tasa de cambio se corrige o el proveedor disputa un precio, no se puede verificar qué se recibió originalmente. Siempre preservar los valores originales junto con los normalizados.

Formato Canónico Diseñado por un Solo Consumidor

Diseñar el formato canónico basándose en las necesidades de un solo consumidor (por ejemplo, el catálogo online). Cuando otros consumidores (pricing, inventario, analytics) se conectan, el formato no tiene los campos que necesitan y hay que modificarlo. El formato canónico debe diseñarse considerando las necesidades de todos los consumidores conocidos y anticipados.

No Validar Post-Normalización

Confiar en que los translators siempre producen mensajes válidos sin validar contra el schema. Un bug en un translator puede producir mensajes con campos faltantes o tipos incorrectos que llegan a los consumidores y causan errores aguas abajo. La validación post-normalización es la red de seguridad que detecta bugs en los translators.

Translator Monolítico

Un solo translator gigante con un switch/case de 30 ramas, una por proveedor. Cuando el proveedor 17 cambia su formato, se modifica el monolito y se arriesga la estabilidad de los 29 proveedores restantes. Cada proveedor debe tener su propio translator independiente, desplegable por separado.


18. Conclusión Técnica

Normalizer es el patrón que absorbe la heterogeneidad de formatos de múltiples fuentes externas, transformando mensajes diversos en un formato canónico común. Es la combinación de detección de formato, routing al translator apropiado y transformación al modelo canónico.

Cuándo aporta valor: cuando múltiples fuentes envían datos semánticamente equivalentes en formatos diferentes y los consumidores necesitan procesarlos uniformemente. Es especialmente valioso cuando el número de fuentes × el número de consumidores hace que las integraciones punto a punto sean ingobernables.

Cuándo evita problemas importantes: cuando sin normalización, cada consumidor implementa sus propios parsers para cada fuente, creando duplicación de lógica, inconsistencias de datos y fragilidad ante cambios de formato.

Cuándo no conviene adoptarlo: cuando todas las fuentes ya usan el mismo formato, cuando hay una sola fuente, o cuando la transformación es tan simple que un Message Translator inline en el consumidor es suficiente.

Recomendación para arquitectos: invierta tanto en normalización de valores como en normalización de estructura. Colores, monedas, unidades de medida y códigos deben homogeneizarse, no solo la estructura del JSON o XML. Diseñe el formato canónico con todos los consumidores en mente, no solo con el primero. Implemente validación post-normalización como red de seguridad contra bugs en los translators. Use translators independientes por fuente (no un monolito). Y siempre preserve los valores originales junto con los normalizados — la trazabilidad del dato original al normalizado es esencial para debugging, auditoría y disputas comerciales.