Módulo 5: Extraer un servicio

Presentación del módulo: extraer un servicio

Por qué este módulo existe aquí

Hasta aquí modernizaste el legacy sin sacarlo del monolito. En el módulo 3 pusiste un facade delante del GET /products y desviaste el tráfico HTTP del viejo al nuevo por porcentaje —pero el "nuevo" vivía al lado, en el mismo despliegue—. En el módulo 4 insertaste una abstracción en el código y migraste la implementación del cálculo de envío por dentro, con un flag —pero seguía siendo el mismo proceso, la misma base de datos—. Los dos patrones cambian quién ejecuta una funcionalidad; ninguno la saca del monolito. Este módulo da ese paso: extraer un bounded context a su propio servicio, con su propio modelo y sus propios datos.

Aquí está la idea completa, en una frase: eliges una pieza del monolito con cohesión propia —el catalog de Mercado—, la sacas a un servicio que tiene un modelo limpio (no la jerga del legacy) y duena sus propios datos (no comparte tablas para siempre), y pones un traductor en el borde que convierte entre el modelo viejo del monolito y el modelo nuevo del servicio, en ambas direcciones, para que ninguno contamine al otro. Ese traductor —el anti-corruption layer— es lo que permite que el monolito siga funcionando sin cambiar una línea mientras el servicio nuevo respira con un diseño propio.

Este módulo enseña esa mecánica a fondo, aplicada al catálogo de Mercado. Recuerda el caso: Mercado es un monolito legacy con catalog, orders, payments y shipping viviendo en un solo código, sobre una sola base de datos. El monolito llama a catalog como una función interna que habla su modelo viejo —prod_id, desc, prc_cents—. Vamos a sacar catalog a un servicio propio que hable un modelo limpio —Product(id, name, price_cents)—, con un anti_corruption_layer que traduzca entre los dos, y a llevar sus datos de la BD compartida a una BD propia, todo sin que el monolito se entere.

Conexión con el módulo. Esta es la lección-mapa. No entramos todavía al detalle de cada paso; instalamos la metáfora (independizar a un hijo: primero vive en casa, luego renta con tu aval, luego su propia casa y sus propias cuentas; el ACL como el traductor entre dos que hablan idiomas distintos), el ciclo completo (encontrar el bounded context → insertar el ACL → duenar los datos → cortar la BD compartida) y el vocabulario (bounded_context, anti_corruption_layer, legacy_model, Product, translate, shared_db, owned_db). Y corremos un ejemplo-mapa: un anti_corruption_layer mínimo que traduce el modelo viejo al modelo limpio y de vuelta, para ver que el monolito no cambia mientras el servicio habla otro idioma. La lección 2 encuentra el bounded context; la 3 construye el ACL; la 4 lo hace en ambas direcciones; la 5 le da al servicio la propiedad de sus datos; la 6 corta la BD compartida; la 7 decide el orden de extracción; y la 8 lo integra en el catálogo de Mercado. Fíjate en la frontera: aquí enseñamos cómo se extrae una pieza (el ACL, los datos, el corte). A dónde llega el servicio (microservicio, eventos, API) es de las guías de estilos; migrar sus datos sin downtime es el módulo 6; y el strangler que desviaba el tráfico al servicio fue el módulo 3.

Y la promesa de siempre: nada se afirma "de memoria", todo se ejecuta. Cada simulación corre con Python 3.14 y solo la biblioteca estándar, con datos fijos, así que la salida que ves en cada bloque "Qué esperar" es la salida literal de correr el código. Puedes copiarlo y reproducirlo idéntico.

Una analogía: independizar a un hijo

Piensa en un hijo que se independiza de casa. No pasa de vivir contigo a ser autosuficiente de un día para otro; pasa por etapas, y en cada una es un poco más dueño de su vida.

Etapa 1 — vive en casa. Duerme en su cuarto, come de tu refrigerador, sus cuentas están mezcladas con las tuyas: la luz, el agua, el internet, todo va en el mismo recibo. No hay frontera clara entre sus gastos y los tuyos. Esto es el bounded context que todavía comparte la base de datos del monolito: la funcionalidad existe, pero sus datos están revueltos con los de todos los demás, en las mismas tablas.

Etapa 2 — renta con tu aval. Se muda a su propio departamento, pero el contrato lo firmas tú como aval, y quizás le pasas dinero al principio. Ya tiene su espacio y empieza a tomar sus decisiones, pero todavía depende de ti para lo importante. Esto es el servicio extraído pero transitorio: ya tiene su propio proceso y su propio modelo, pero sus datos siguen apoyados en la BD compartida mientras se termina el corte.

Etapa 3 — su propia casa y sus propias cuentas. Compra o renta a su nombre, paga sus propios recibos, decide qué come sin consultarte. Es autosuficiente. Esto es el servicio con la propiedad completa de sus datos: su propia base de datos (owned_db), su propio modelo, y ninguna dependencia de las tablas del monolito. La shared_db se cortó.

Y aquí entra la pieza clave, la que le da nombre al módulo. Imagina que el hijo se muda a otro país y ahora habla otro idioma, mientras tú sigues hablando el de siempre. Para que sigan negociando —quién paga qué, cuándo se visitan— necesitan un traductor que convierta lo que dice cada uno al idioma del otro, en las dos direcciones. Ese traductor no cambia lo que ninguno piensa; solo evita que se malentiendan. El anti-corruption layer es ese traductor: el monolito sigue hablando su modelo viejo, el servicio habla su modelo nuevo, y el ACL traduce entre los dos para que ninguno tenga que aprender el idioma del otro —y, sobre todo, para que el idioma sucio del viejo no se cuele en el diseño limpio del nuevo, ni al revés—.

Aquí están las piezas del módulo, ya visibles en la analogía:

  • El bounded context es el hijo: una unidad con su propia identidad, que vale la pena independizar.
  • Compartir la BD (shared_db) es la etapa de vivir en casa con las cuentas revueltas.
  • Duenar los datos (owned_db) es la etapa de su propia casa con sus propias cuentas.
  • El anti-corruption layer es el traductor entre dos que hablan idiomas distintos.
  • El orden de extracción es empezar por el hijo más independiente, no por el que todavía depende de todos.

Ejemplo trabajado: un anti_corruption_layer mínimo traduciendo el catalog de Mercado

No vamos a describir el traductor: lo vamos a ejecutar, aunque sea en su versión más pequeña. La idea es tener las tres piezas mínimas —el modelo viejo del monolito, el modelo limpio del servicio, y el anti_corruption_layer que traduce entre ellos— y ver, con nuestros ojos, que el monolito puede seguir hablando su idioma viejo mientras el servicio habla uno limpio, con el ACL en medio.

El monolito guarda cada producto como un diccionario con nombres crípticos y tipos sucios: prod_id (el id), desc (el nombre, pero el campo se llama "desc" de descripción, herencia del legacy), prc_cents (el precio en centavos, pero guardado como string), y act (activo, pero como 'Y'/'N'). El servicio nuevo quiere hablar un modelo limpio: un Product con id, name, price_cents (int de verdad) y active (bool de verdad). El anti_corruption_layer traduce en ambas direcciones: to_modern convierte el dict viejo en un Product limpio, y to_legacy lo convierte de vuelta.

from dataclasses import dataclass

# --- El modelo VIEJO: la forma que usa el monolito legacy de Mercado hoy. ---
# Nombres cripticos, precio como string, activo como "Y"/"N": el legacy real.
legacy_rows = [
    {"prod_id": 1, "desc": "SSD 1TB",   "prc_cents": "8999",  "act": "Y"},
    {"prod_id": 2, "desc": "USB-C Hub", "prc_cents": "3200",  "act": "Y"},
    {"prod_id": 3, "desc": "Webcam HD", "prc_cents": "0",     "act": "N"},
]

# --- El modelo NUEVO, limpio: el que el servicio de catalogo quiere hablar. ---
@dataclass
class Product:
    id: int
    name: str
    price_cents: int
    active: bool

# --- El anti-corruption layer: traduce viejo <-> nuevo en el borde. ---
def to_modern(row):
    # legacy -> modern: nombres claros, tipos correctos, sin jerga del viejo.
    return Product(
        id=row["prod_id"],
        name=row["desc"],
        price_cents=int(row["prc_cents"]),
        active=(row["act"] == "Y"),
    )

def to_legacy(product):
    # modern -> legacy: de vuelta a la forma que el monolito espera recibir.
    return {
        "prod_id": product.id,
        "desc": product.name,
        "prc_cents": str(product.price_cents),
        "act": "Y" if product.active else "N",
    }

# --- El monolito llamaba a catalog como funcion interna con el modelo viejo. ---
# Esta funcion NO cambia: sigue recibiendo y devolviendo el dict legacy.
def legacy_catalog_lookup(prod_id):
    for row in legacy_rows:
        if row["prod_id"] == prod_id:
            return row
    return None

print("El ACL traduce el modelo viejo del monolito <-> el modelo limpio del servicio\n")
for row in legacy_rows:
    prod = to_modern(row)
    print(f"  legacy: {row}")
    print(f"  modern: {prod}\n")

print("Round-trip: legacy -> modern -> legacy debe volver identico\n")
print(f"{'prod_id':>8}{'ida y vuelta identica?':>26}")
print("-" * 34)
all_ok = True
for row in legacy_rows:
    back = to_legacy(to_modern(row))
    ok = back == row
    all_ok = all_ok and ok
    print(f"{row['prod_id']:>8}{('SI' if ok else 'NO'):>26}")

print("-" * 34)
print(f"\nRound-trip intacto en todas las filas: {all_ok}")
print(f"El monolito sigue igual: legacy_catalog_lookup(2) = {legacy_catalog_lookup(2)}")
print("\n  El monolito no cambio (habla legacy). El servicio habla Product (limpio).")
print("  El ACL es el traductor en el borde: nadie contamina al otro.")

Qué esperar. Al correr el archivo, la salida es exactamente esta:

El ACL traduce el modelo viejo del monolito <-> el modelo limpio del servicio

  legacy: {'prod_id': 1, 'desc': 'SSD 1TB', 'prc_cents': '8999', 'act': 'Y'}
  modern: Product(id=1, name='SSD 1TB', price_cents=8999, active=True)

  legacy: {'prod_id': 2, 'desc': 'USB-C Hub', 'prc_cents': '3200', 'act': 'Y'}
  modern: Product(id=2, name='USB-C Hub', price_cents=3200, active=True)

  legacy: {'prod_id': 3, 'desc': 'Webcam HD', 'prc_cents': '0', 'act': 'N'}
  modern: Product(id=3, name='Webcam HD', price_cents=0, active=False)


Round-trip: legacy -> modern -> legacy debe volver identico

 prod_id    ida y vuelta identica?
----------------------------------
       1                        SI
       2                        SI
       3                        SI
----------------------------------

Round-trip intacto en todas las filas: True
El monolito sigue igual: legacy_catalog_lookup(2) = {'prod_id': 2, 'desc': 'USB-C Hub', 'prc_cents': '3200', 'act': 'Y'}

  El monolito no cambio (habla legacy). El servicio habla Product (limpio).
  El ACL es el traductor en el borde: nadie contamina al otro.

Lee la salida en tres tramos, porque cada uno muestra una cara del módulo.

El primer tramo muestra la traducción de ida (legacy → modern) fila por fila. El monolito tiene {'prod_id': 1, 'desc': 'SSD 1TB', 'prc_cents': '8999', 'act': 'Y'} —un diccionario con nombres oscuros y un precio guardado como texto—, y el ACL lo convierte en Product(id=1, name='SSD 1TB', price_cents=8999, active=True) —un objeto con nombres claros, el precio como número entero, y el estado como booleano—. Fíjate en el producto 3: el '0' string se volvió 0 int, y el 'N' se volvió active=False. El ACL no solo renombra campos; normaliza los tipos y el dominio. Del ACL hacia adentro, el servicio nunca ve un 'Y' ni un precio-como-texto: ve bool e int limpios.

El segundo tramo es el round-trip: legacy → modern → legacy vuelve idéntico en las tres filas (SI, SI, SI), y la línea Round-trip intacto en todas las filas: True lo confirma en una sola verificación. Esto importa porque el ACL traduce en ambas direcciones: si toma el dict viejo, lo convierte en Product, y lo convierte de vuelta, tiene que regresar exactamente el dict con el que empezó. Si el round-trip fallara, el ACL estaría perdiendo o deformando información en la traducción —y el monolito, que espera su modelo viejo intacto, recibiría algo distinto—.

El tercer tramo es el corazón del módulo en una línea: El monolito sigue igual: legacy_catalog_lookup(2) = {'prod_id': 2, ...}. La función interna del monolito no cambió; sigue recibiendo y devolviendo el dict legacy de siempre. Instalamos un servicio con un modelo limpio y un traductor en el borde sin tocar el monolito. Eso es una extracción bien hecha: el hijo se mudó y aprendió otro idioma, pero tú sigues hablando el tuyo, y el traductor evita que se malentiendan.

Que el round-trip dé True y que el monolito siga idéntico no es un detalle: es la propiedad que hace segura la extracción. Igual que en el strangler el facade a 0% era transparente, aquí el ACL —bien construido— deja al monolito exactamente como estaba, mientras el servicio nuevo empieza a vivir su propia vida con un diseño propio.

El ciclo completo de la extracción

Ese ejemplo tocó las piezas del patrón sin desarrollarlas. Vale la pena ver el orden en que se instalan, porque es la columna vertebral de las lecciones que siguen:

Paso                          Leccion   Que haces                              Datos del servicio
────────────────────────────  ────────  ─────────────────────────────────────  ──────────────────
1. Encontrar el bounded ctx   L2        medir el seam, elegir la hoja           en shared_db
2. Insertar el ACL            L3-L4     traducir viejo <-> nuevo, ambos sentidos  en shared_db
3. Duenar los datos           L5        el servicio es el unico que lee/escribe  copia a owned_db
4. Cortar la BD compartida    L6        shared_db -> owned_db, monolito igual     en owned_db

Un matiz de orden, como aviso: el ACL (paso 2) se inserta antes de cortar los datos (pasos 3 y 4), no después. Primero pones el traductor en el borde —el servicio ya habla su modelo limpio, aunque sus datos sigan en la BD compartida—, y solo cuando el ACL está probado en ambas direcciones cortas la dependencia de datos. La lección 6 lo trata a fondo; el orden de la tabla es lógico.

Y así encajan los pasos en el flujo del módulo:

flowchart LR
    C["monolito<br/>(habla legacy)"] --> ACL["anti_corruption_layer<br/>(traduce viejo <-> nuevo)"]
    ACL -->|to_modern| S["catalog service<br/>(habla Product)"]
    S -->|to_legacy| ACL
    S -.->|paso 3-4| DB["owned_db<br/>(datos propios)"]
    C -.->|deja de tocar| SDB["shared_db<br/>(se retira)"]

Léelo así: el monolito ya no llama a catalog como función interna; le habla al servicio a través del ACL. El ACL traduce la request del modelo viejo (to_modern) al modelo limpio que el servicio entiende, y traduce la respuesta de vuelta (to_legacy) al modelo que el monolito espera. Con el tiempo, el servicio deja de leer la shared_db y pasa a duenar su owned_db, y el monolito deja de tocar la tabla compartida. Al final, catalog es un servicio autosuficiente y el monolito es una rebanada más pequeño.

El mapa: dónde está este módulo en la guía

Extraer un servicio es el tercer patrón de migración, y el que más lejos llega: no solo cambia quién ejecuta, sino la topología —una pieza sale del monolito a su propio proceso, con su propio modelo y sus propios datos—. Así se conecta con el resto:

flowchart TD
    M1["M1 · Por que no reescribir<br/>(la conviccion)"]
    M2["M2 · Caracterizar el legacy<br/>(la red de seguridad)"]
    M3["M3 · Strangler fig<br/>(migrar por fuera, por trafico)"]
    M4["M4 · Branch by abstraction<br/>(migrar por dentro, por codigo)"]
    M5["M5 · Extraer un servicio<br/>(anti-corruption layer, datos propios)"]
    M6["M6 · Migrar datos sin downtime"]
    M7["M7 · Medir el progreso"]
    M1 --> M2 --> M3 --> M4 --> M5 --> M6 --> M7

Léelo así: en M1 te convenciste de que incremental gana; en M2 pusiste la red de seguridad con characterization tests; en M3 aprendiste a desviar tráfico con un facade externo; en M4 aprendiste a migrar la implementación por dentro del código; aquí, en M5, sacas la pieza a su propio servicio con un ACL y datos propios; en M6 migras esos datos sin downtime; y en M7 mides el avance hasta apagar lo viejo. Los tres patrones de migración —strangler (M3), branch by abstraction (M4) y extracción (M5)— son primos: la extracción a menudo usa a los otros dos como herramientas (un strangler desvía el tráfico al servicio nuevo; una abstracción interna prepara el corte), pero va más allá: cambia dónde vive la pieza.

Y la frontera con las guías hermanas del ecosistema: esta guía es la técnica de extracción, no el destino. A dónde llega el catalog extraído —si termina siendo un microservicio, un servicio orientado a eventos o una API pública— lo enseñan architectural-styles-and-boundaries, event-driven-architecture y api-design-and-integration. La decisión de extraer —el ADR, el costo, la reversibilidad— es architecture-decisions-and-tradeoffs. Y migrar los datos del servicio sin downtime —dual-write, backfill, parallel-run— es el módulo 6: aquí cortamos la propiedad de los datos (de quién son las tablas), no la mecánica de mover los registros sin apagar el sistema.

Errores comunes

Creer que extraer es "mover el código a otra carpeta". Qué pasa: el equipo toma la función catalog del monolito, la copia a un módulo nuevo llamado catalog_service, y da la extracción por hecha —pero el "servicio" sigue leyendo las mismas tablas del monolito y hablando el mismo modelo viejo—. Por qué pasa: mover archivos es visible y rápido; cortar la dependencia de datos y traducir el modelo es tedioso e invisible. Cómo detectarlo: pregunta "¿de qué tablas lee este servicio?". Si la respuesta incluye las tablas del monolito, no está extraído, solo está reubicado. Cómo corregirlo: la extracción tiene dos cortes que "mover el código" no hace —el corte de modelo (el ACL traduce viejo↔nuevo, para que el servicio hable limpio) y el corte de datos (el servicio duena su store, no comparte tablas)—. Sin esos dos cortes, no extrajiste nada; movíste una carpeta. Las lecciones 3 a 6 son exactamente esos dos cortes.

Extraer sin ACL y arrastrar el modelo viejo al servicio nuevo. Qué pasa: para "ir rápido", el equipo hace que el servicio nuevo hable directamente el modelo viejo del monolito —prod_id, desc, prc_cents como string— sin traductor. Por qué pasa: escribir el ACL se siente como trabajo extra; parece más fácil que el nuevo use el modelo que ya existe. Cómo detectarlo: el código del servicio "nuevo" está lleno de int(row["prc_cents"]) y row["act"] == "Y" —la jerga del viejo, colada en el diseño nuevo—. Cómo corregirlo: el ACL existe justo para evitar esa contaminación. Si el servicio nuevo hereda el modelo sucio del viejo, nace con la misma deuda que querías dejar atrás, y en unos meses es tan enredado como el monolito. La lección 3 muestra por qué el modelo limpio importa: la lógica del servicio se lee sola (price_cents == 0) en vez de parsear strings. El traductor en el borde es lo que compra esa limpieza.

Ejercicios

Ejercicio 1 — Las etapas del hijo. Con la analogía de independizar a un hijo, empareja cada etapa con lo que ocurre en la extracción: (a) vive en casa con las cuentas revueltas, (b) renta con tu aval, (c) su propia casa y sus propias cuentas. Luego di qué representa el traductor entre dos que hablan idiomas distintos, y por qué hace falta en ambas direcciones.

Ver solución
  • (a) Vive en casa con las cuentas revueltas → el bounded context que comparte la shared_db. La funcionalidad existe, pero sus datos están mezclados con los de todos los demás en las mismas tablas: no hay frontera de datos.
  • (b) Renta con tu aval → el servicio extraído pero transitorio. Ya tiene su propio proceso y su propio modelo, pero sus datos siguen apoyados en la BD compartida mientras se termina el corte.
  • (c) Su propia casa y sus propias cuentas → el servicio con owned_db. Duena completamente sus datos; la dependencia de las tablas del monolito se cortó. Es autosuficiente.

El traductor representa el anti-corruption layer: convierte lo que dice cada lado al idioma del otro. Hace falta en ambas direcciones porque los dos lados hablan idiomas distintos y los dos tienen que entenderse: cuando el monolito le pide algo al servicio, hay que traducir del modelo viejo al nuevo (to_modern), para que el servicio lo entienda limpio; y cuando el servicio responde, hay que traducir del modelo nuevo al viejo (to_legacy), para que el monolito reciba lo que espera. Si el traductor solo tradujera en un sentido, la mitad de la conversación quedaría en un idioma que el otro no entiende —y algo se rompería—.

Ejercicio 2 — Lee el round-trip. En el ejemplo, el round-trip legacy → modern → legacy volvió idéntico en las tres filas (True). (a) ¿Por qué es importante que vuelva idéntico y no solo "equivalente"? (b) ¿Qué información tuvo que preservar el ACL para que el '8999' string volviera exactamente como '8999' y no como 8999? (c) Si el round-trip fallara en una fila, ¿qué te diría sobre el ACL?

Ver solución

(a) Porque el monolito no cambió: sigue esperando su modelo viejo exactoprc_cents como string, act como 'Y'/'N'—. Si el round-trip volviera "equivalente pero distinto" (por ejemplo, prc_cents como 8999 int en vez de '8999' string), el monolito recibiría un tipo que no espera y podría romperse al procesarlo. La transparencia hacia el monolito exige igualdad total, no equivalencia.

(b) El ACL tuvo que recordar, en la dirección de vuelta (to_legacy), que el monolito guarda el precio como string: por eso to_legacy hace str(product.price_cents), convirtiendo el 8999 int limpio del Product de vuelta al '8999' string que el monolito espera. La traducción de ida normaliza (int(row["prc_cents"])) y la de vuelta des-normaliza (str(...)). El ACL conoce las dos formas y sabe cuál corresponde a cada lado.

(c) Que el ACL está perdiendo o deformando información en la traducción. Un round-trip que no vuelve idéntico significa que to_modern seguido de to_legacy no es la identidad: quizás to_legacy olvidó un campo, o convirtió un tipo de forma incorrecta. Es una señal de que el traductor todavía no es fiel, y de que el monolito recibiría algo distinto de lo que le dio. Antes de confiar en la extracción, el round-trip tiene que dar True en todos los casos, incluidos los borde (precio cero, producto inactivo).

Ejercicio 3 — ¿Extraer, estrangular o branch by abstraction? Para cada situación, di cuál de los tres patrones primos aplica y por qué: (a) desviar el tráfico HTTP del GET /products del viejo al nuevo por porcentaje; (b) cambiar la implementación de calculate_shipping(), una función interna, por dentro del código con un flag; (c) sacar todo el catalog a su propio proceso, con su propio modelo y su propia base de datos.

Ver solución
  • (a) Desviar el tráfico de GET /products por porcentaje → strangler fig (módulo 3). Hay una frontera de red (un endpoint HTTP) donde interponer un facade que decida, request por request, viejo vs nuevo. El punto de interposición es el tráfico.
  • (b) Cambiar calculate_shipping() por dentro con un flag → branch by abstraction (módulo 4). Es una función interna, sin frontera de red. El cambio se hace insertando una abstracción en el código y migrando la implementación detrás de ella. El punto de interposición es el código.
  • (c) Sacar catalog a su propio proceso con su modelo y su BD → extraer un servicio (este módulo, 5). Aquí el objetivo no es solo cambiar quién ejecuta, sino mover la pieza a su propia topología: su proceso, su modelo limpio (con un ACL que traduce), y sus datos propios (con el corte de la BD compartida). La extracción a menudo usa el strangler (para desviarle el tráfico) y branch by abstraction (para preparar el corte) como herramientas, pero va más allá: cambia dónde vive la pieza y de quién son sus datos.

La lección de fondo: los tres patrones logran migración incremental, y se distinguen por cuánto mueven. El strangler mueve el tráfico; branch by abstraction mueve la implementación; la extracción mueve la pieza entera —código, modelo y datos— fuera del monolito. Este módulo es el más ambicioso de los tres.

Resumen y siguiente paso

En esta lección instalaste el tercer patrón de migración, el que rompe el monolito de verdad: extraer un bounded context a su propio servicio. Viste su idea completa —encontrar la pieza, sacarla a un servicio con modelo limpio y datos propios, y poner un traductor en el borde— y su metáfora: el hijo que se independiza por etapas (vive en casa con las cuentas revueltas → renta con tu aval → su propia casa y sus propias cuentas), con el ACL como el traductor entre dos que hablan idiomas distintos. Y lo ejecutaste en su versión mínima: un anti_corruption_layer que tradujo el modelo viejo del monolito (prod_id, desc, prc_cents) al modelo limpio del servicio (Product) y de vuelta, con round-trip verificado, mientras el monolito seguía idéntico.

Antes de avanzar deberías poder: nombrar los cuatro pasos de la extracción y en qué lección se desarrolla cada uno; explicar por qué el ACL traduce en ambas direcciones; leer un round-trip y entender por qué tiene que volver idéntico; y distinguir cuándo aplica la extracción de cuándo aplican el strangler y branch by abstraction.

La lección 2 hace el primer paso: encontrar el bounded context. Antes de extraer, hay que decidir qué extraer, y no todo pedazo del monolito es un buen candidato. Vas a aprender qué es un bounded context, y vas a medir el seam de los cuatro módulos de Mercado —las llamadas que cruzan la frontera de cada uno, cuántas entran y cuántas salen— para ver, con números, por qué catalog es la hoja limpia que conviene extraer primero. La extracción empieza por elegir bien la pieza.

Recursos

  • Eric Evans, Domain-Driven Design (Addison-Wesley, 2003), capítulos sobre Bounded Context y Anti-Corruption Layer — la fuente original de los dos conceptos centrales de este módulo. Evans define el bounded context como el límite dentro del cual un modelo es consistente, y el anti-corruption layer como la capa que traduce entre dos modelos para que uno no corrompa al otro. La lectura fundacional del módulo. En inglés.
  • Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 3, "Splitting the Monolith" — el capítulo que trata la extracción de servicios como técnica: elegir qué extraer, los patrones de base de datos (tabla compartida transitoria, la propiedad de los datos), y cómo cortar la dependencia. La referencia de cabecera para las lecciones 5 y 6. En inglés.
  • Martin Fowler, "BoundedContext" — martinfowler.com/bliki/BoundedContext.html. La ficha corta y clara del concepto: por qué un modelo grande es más fácil de manejar dividido en contextos con fronteras explícitas. En inglés.
  • Chris Richardson, "Pattern: Decompose by subdomain" — microservices.io/patterns/decomposition/decompose-by-subdomain.html. La ficha del patrón de descomponer un monolito por subdominio (bounded context), en el catálogo de microservicios. Corta y directa. En inglés.