Módulo 8: Proyecto — modernizar una rebanada de Mercado

Extraer el servicio con un anti-corruption layer

Descripción

En el paso anterior, el modern corría "al lado" del legacy detrás del facade, pero seguía siendo un pedazo del mismo sistema. El tercer paso del método —el módulo 5 hecho acción— es la extracción de verdad: sacar el catalog a su propio servicio, con su propio modelo limpio y sus propios datos, sin que el monolito cambie una sola línea. La pieza que lo hace posible es el anti-corruption layer (ACL): un traductor que convierte el modelo viejo del monolito (prod_id, desc, prc_cents como string) al modelo limpio del servicio (Product) y de vuelta, en las dos direcciones, para que cada mundo viva encerrado en su propio idioma.

La extracción no es un salto único; es un diario por fases, y cada fase cambia las tripas del catalog un poco más mientras el monolito sigue recibiendo exactamente lo de siempre. Empieza como una función interna del monolito (el catalog es una función que devuelve el modelo viejo directo). Se convierte en un servicio con ACL que trabaja en Product pero todavía lee los datos de la BD compartida (shared_db). Luego los datos se mudan a su BD propia (owned_db), donde el servicio los guarda ya en su modelo limpio. Y finalmente el servicio es autónomo —modelo propio, datos propios— y la función interna del monolito se borra. Cuatro fases, un cambio total por dentro, y el monolito sin enterarse.

Lo que hace segura toda esta transformación es una propiedad que vas a verificar con dos medidas que tienen que dar lo mismo (o True): que el monolito renderice idéntico en las cuatro fases —el contrato viejo intacto— y que el round-trip del ACL vuelva fiel —to_legacy(to_modern(row)) == row—. Si el monolito ve lo mismo en la fase 1 (función interna) y en la fase 4 (servicio autónomo con datos propios), la extracción fue invisible para él, que es justo la promesa: modernizar sin obligar a los cientos de consumidores del catalog dentro del monolito a cambiar.

Conexión con el módulo. Este es el tercer paso del método (M5), y produce dos cosas para el paso siguiente: el servicio con su modelo y sus datos propios (owned_db en modelo Product) y el ACL de vuelta, que mantiene el contrato del monolito intacto pase lo que pase con los datos por dentro. La lección 5 se apoya en ese ACL para migrar los datos con seguridad: el ACL de vuelta es lo que garantiza que, mientras los datos se mudan de un store a otro, el monolito siga recibiendo su modelo viejo exacto. Fíjate en la frontera: aquí ejecutamos la técnica de extracción —el ACL, la propiedad de los datos, la BD compartida transitoria que se corta—. A dónde llega el servicio extraído (si termina siendo un microservicio, un servicio de eventos, una API pública) y cómo se diseña esa forma destino lo enseñan las guías hermanas; la lección 8 te dice cuáles.

Una analogía: mudar la cocina de un restaurante sin cerrar el comedor

Piensa en un restaurante que va a modernizar su cocina por completo —cambiar todo: los métodos, los ingredientes, hasta el idioma en que el chef da las órdenes— pero con una regla inviolable: el comedor no cambia y los clientes no se enteran. El cliente pide "el filete término medio" como siempre, y recibe "el filete término medio" como siempre, en el mismo plato, a la misma temperatura. Lo que pasa detrás de la puerta de la cocina puede transformarse radicalmente; lo que cruza esa puerta hacia el comedor tiene que ser idéntico a lo de siempre.

La clave para lograrlo es un traductor en la puerta de la cocina. La orden entra en el idioma del comedor ("filete término medio, sin sal") y el traductor la convierte al idioma nuevo de la cocina (los códigos, las estaciones, el flujo moderno). El plato sale en el idioma de la cocina nueva y el traductor lo devuelve al comedor exactamente como el cliente lo espera. Gracias a ese traductor, la cocina puede mudarse por fases —primero cambian los cuchillos, luego el método de cocción, luego hasta de dónde llega la carne— sin que una sola mesa note la diferencia. Y hay una prueba que el gerente hace en cada fase: pide un plato, lo compara con cómo salía antes, y si es idéntico, la mudanza de esa fase fue invisible.

El ACL es ese traductor en la puerta. La request del monolito entra en modelo viejo (prod_id, desc, prc_cents), el ACL la traduce a Product para que el servicio nuevo la entienda, el servicio responde en Product, y el ACL la traduce de vuelta al modelo viejo antes de entregarla al monolito. La cocina (el catalog) se muda por fases —de función interna a servicio, de datos compartidos a datos propios—, pero el comedor (el monolito) recibe siempre su plato de siempre. Y la prueba del gerente es tu verificación: renderizar en cada fase y comprobar que el monolito ve lo mismo.

Ejemplo trabajado: el diario de extracción en cuatro fases

Vamos a ejecutar la extracción completa del catalog como un diario de cuatro fases. El ACL —to_modern y to_legacy— traduce entre el modelo viejo (dict con prod_id, desc, prc_cents string, act) y el Product limpio. En cada fase, el catalog obtiene el dato de un lugar distinto y con un modelo distinto por dentro, pero devuelve el mismo dict legacy que el monolito consume. Verificamos dos cosas: que el monolito renderice idéntico en las cuatro fases, y que el round-trip del ACL vuelva fiel para cada producto.

# Paso 3 del metodo: extraer el catalog a su propio servicio con un anti-corruption
# layer que traduce el modelo viejo (prod_id, desc, prc_cents string) al limpio
# (Product) y de vuelta. Diario por fases: funcion interna -> ACL sobre shared_db ->
# owned_db -> servicio autonomo. El monolito recibe IDENTICO en las cuatro fases.

from dataclasses import dataclass


@dataclass
class Product:
    id: int
    name: str
    price_cents: int
    active: bool


# --- ACL: dos traducciones, una por sentido. ---
def to_modern(row):
    return Product(row["prod_id"], row["desc"], int(row["prc_cents"]), row["act"] == "Y")


def to_legacy(product):
    return {"prod_id": product.id, "desc": product.name,
            "prc_cents": str(product.price_cents), "act": "Y" if product.active else "N"}


LEGACY_KEYS = {"prod_id", "desc", "prc_cents", "act"}

# El dato del catalog, en el modelo viejo (como vive en shared_db del monolito).
shared_db = [
    {"prod_id": 1, "desc": "SSD 1TB",   "prc_cents": "8999", "act": "Y"},
    {"prod_id": 3, "desc": "Webcam HD", "prc_cents": "5999", "act": "N"},
]

# El servicio, tras la extraccion, guarda sus datos como Product en su store propio.
owned_db = [to_modern(r) for r in shared_db]


# El monolito SOLO sabe leer el modelo viejo. Su render no cambia en ninguna fase.
def monolith_render(legacy):
    assert set(legacy) == LEGACY_KEYS, "el monolito solo acepta el dict legacy"
    return f"[{legacy['prod_id']}] {legacy['desc']} - ${int(legacy['prc_cents'])/100:.2f}"


# --- Las cuatro fases de la extraccion. Cada una devuelve el dict legacy que el
#     monolito consume; por dentro, de donde sale el dato cambia por completo. ---
def phase_internal_function(prod_id):
    # Fase 1: el catalog es una FUNCION INTERNA del monolito. Lee shared_db y
    # devuelve el modelo viejo directo. No hay servicio ni ACL todavia.
    row = next(r for r in shared_db if r["prod_id"] == prod_id)
    return dict(row)


def phase_acl_over_shared(prod_id):
    # Fase 2: el catalog ya es un SERVICIO con modelo Product, pero sus datos
    # siguen en shared_db. El ACL traduce a la entrada y a la salida.
    row = next(r for r in shared_db if r["prod_id"] == prod_id)
    product = to_modern(row)            # ACL de entrada: legacy -> Product
    return to_legacy(product)           # ACL de salida: Product -> legacy


def phase_owned_db(prod_id):
    # Fase 3: los datos se mudaron a owned_db (Product nativo). El servicio los lee
    # nativos; el ACL solo traduce a la salida. shared_db ya no es la fuente.
    product = next(p for p in owned_db if p.id == prod_id)
    return to_legacy(product)           # ACL de salida: Product -> legacy


def phase_autonomous_service(prod_id):
    # Fase 4: el servicio es autonomo (owned_db, modelo propio) y la funcion interna
    # del monolito ya se borro. El monolito solo lo consume por el facade.
    product = next(p for p in owned_db if p.id == prod_id)
    return to_legacy(product)


phases = [
    ("1. funcion interna",     "modelo viejo", phase_internal_function),
    ("2. ACL sobre shared_db", "Product+ACL",  phase_acl_over_shared),
    ("3. owned_db",            "Product+ACL",  phase_owned_db),
    ("4. servicio autonomo",   "Product+ACL",  phase_autonomous_service),
]

print("Diario de extraccion del catalog: 4 fases, el monolito ve lo MISMO\n")
print(f"{'fase':<24}{'por dentro':<14}{'el monolito renderiza'}")
print("-" * 78)
renders = []
for name, inside, fn in phases:
    legacy = fn(prod_id=1)
    rendered = monolith_render(legacy)
    renders.append(rendered)
    print(f"{name:<24}{inside:<14}{rendered}")
print("-" * 78)
print(f"\n  Las 4 fases renderizan identico?  {len(set(renders)) == 1}")

# --- Verificacion del ACL: el round-trip debe volver identico para cada producto. ---
print("\nRound-trip del ACL: to_legacy(to_modern(row)) == row")
all_ok = True
for row in shared_db:
    back = to_legacy(to_modern(row))
    ok = back == row
    all_ok = all_ok and ok
    print(f"  prod_id={row['prod_id']}: round-trip {'OK' if ok else 'ROTO'}")
print(f"\n  round_trip global: {all_ok}")
print("  El monolito respondio identico en las 4 fases mientras sus tripas pasaban")
print("  de funcion interna a servicio autonomo con datos propios. El ACL sostuvo")
print("  el contrato viejo intacto en cada fase: extraer sin que el monolito lo note.")

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

Diario de extraccion del catalog: 4 fases, el monolito ve lo MISMO

fase                    por dentro    el monolito renderiza
------------------------------------------------------------------------------
1. funcion interna      modelo viejo  [1] SSD 1TB - $89.99
2. ACL sobre shared_db  Product+ACL   [1] SSD 1TB - $89.99
3. owned_db             Product+ACL   [1] SSD 1TB - $89.99
4. servicio autonomo    Product+ACL   [1] SSD 1TB - $89.99
------------------------------------------------------------------------------

  Las 4 fases renderizan identico?  True

Round-trip del ACL: to_legacy(to_modern(row)) == row
  prod_id=1: round-trip OK
  prod_id=3: round-trip OK

  round_trip global: True
  El monolito respondio identico en las 4 fases mientras sus tripas pasaban
  de funcion interna a servicio autonomo con datos propios. El ACL sostuvo
  el contrato viejo intacto en cada fase: extraer sin que el monolito lo note.

Lee el diario fase por fase, mirando la columna por dentro (lo que cambia) contra la columna del render (lo que no cambia).

Fase 1 — función interna. El catalog es una función dentro del monolito que lee shared_db y devuelve el modelo viejo directo, sin traducción. Es el punto de partida: no hay servicio ni ACL, solo código del monolito hablando su propio idioma. El monolito renderiza [1] SSD 1TB - $89.99.

Fase 2 — ACL sobre shared_db. El catalog ya es un servicio con modelo Product. Cuando llega la request, el ACL de entrada traduce la fila de shared_db a Product (to_modern), el servicio trabaja en su modelo limpio, y el ACL de salida traduce de vuelta a legacy (to_legacy) antes de entregar al monolito. Los datos siguen en la BD compartida, pero el modelo del servicio ya es limpio. El monolito renderiza [1] SSD 1TB - $89.99 —idéntico—.

Fase 3 — owned_db. Los datos se mudaron a la BD propia del servicio (owned_db), donde ya viven como Product nativos. El servicio los lee sin traducir a la entrada (ya son Product); solo el ACL de salida traduce a legacy para el monolito. La fuente del dato cambió por completo —de la fila legacy en shared_db al Product nativo en owned_db—, pero el monolito renderiza [1] SSD 1TB - $89.99 —idéntico—.

Fase 4 — servicio autónomo. El servicio es del todo independiente: modelo propio, datos propios, y la función interna del monolito ya se borró. El monolito solo lo consume por el facade. Sigue renderizando [1] SSD 1TB - $89.99 —idéntico—.

Y aquí está el veredicto de la extracción: Las 4 fases renderizan identico? True. Entre la fase 1 (una función dentro del monolito con el modelo viejo) y la fase 4 (un servicio autónomo con su propio modelo y sus propios datos), el catalog se transformó por completo por dentro —cambió qué es, cómo modela los datos y de dónde los saca—, y el monolito no notó absolutamente nada. Cada plato salió idéntico del comedor, aunque la cocina se hubiera mudado entera.

El round_trip global: True es lo que garantiza esa invisibilidad. El round-trip verifica to_legacy(to_modern(row)) == row para cada producto: si tomas una fila legacy, la traduces a Product y la traduces de vuelta, obtienes la fila legacy original, byte por byte —incluido prod_id=3, el producto inactivo (act: "N")—. Esa fidelidad del ACL en las dos direcciones es lo que hace que el monolito reciba siempre su modelo viejo exacto. Sin ella, extraer el servicio obligaría a reescribir a los cientos de consumidores del catalog dentro del monolito —el big-bang que el método existe para evitar—.

Profundización: por qué la dirección de vuelta salva al monolito, y la BD compartida transitoria

Dos ideas de este paso merecen desarrollarse: por qué el ACL de vuelta es la mitad crítica, y qué pasa con la BD compartida durante la extracción.

La dirección de vuelta es la que mantiene la promesa. Es fácil ver por qué hace falta la dirección de entrada (legacy → modern): sin ella, el servicio tendría que hablar el modelo sucio, que es lo que la extracción quiere evitar. Pero la dirección de vuelta (modern → legacy) es la menos obvia y la más crítica para la seguridad, porque es la que cumple la promesa al monolito: "vas a seguir recibiendo exactamente lo que recibías". El monolito tiene, repartidos por su código, cientos de lugares que consumen el catalog —el checkout, el carrito, los emails, el panel de admin—, todos esperando row["desc"] y row["prc_cents"]. Cuando extraes el catalog, no reescribes esos cientos de consumidores para que hablen Product; eso sería el big-bang. En vez de eso, el ACL de vuelta hace que el servicio, visto desde el monolito, se comporte idéntico a la función interna que reemplazó: recibe lo viejo, devuelve lo viejo.

                     ACL bidireccional
                          │
   monolito ──[legacy]───>│───[modern]──> servicio catalog
   (cientos de            │               (modelo Product,
    consumidores,         │                datos propios en owned_db)
    sin cambiar)   <──────│<──────────────
                   [legacy]     [modern]
                          │
   la promesa: el monolito recibe su modelo viejo intacto, pase lo que pase adentro

El round-trip que verificaste es el ACL de vuelta aplicado a un dato que no cambia: to_legacy(to_modern(row)) == row. Si el round-trip da True para todos los casos, sabes que la dirección de vuelta es fiel —que el monolito recibirá exactamente su modelo viejo—. Por eso se verifica: es la prueba de que la promesa al monolito se cumple en cada fase.

La BD compartida es transitoria, y se corta. Fíjate en la transición de la fase 2 (ACL sobre shared_db) a la fase 3 (owned_db). En la fase 2, el servicio ya tiene modelo propio pero comparte los datos con el monolito: ambos leen de shared_db. Ese estado es útil como puente —permite extraer el modelo sin mover los datos todavía—, pero es transitorio y peligroso si se queda: mientras dos sistemas compartan una tabla, ninguno es dueño de verdad de sus datos, un cambio de esquema afecta a los dos, y el acoplamiento que la extracción quería romper sigue vivo en la capa de datos. Por eso el método no se detiene en la fase 2: los datos se mudan a owned_db (fase 3), donde el servicio es el único que los lee y escribe, y la BD compartida se corta. La propiedad de los datos —que el servicio duene su store— es lo que completa la extracción; un "servicio" que aún lee las tablas del monolito no está extraído, solo disfrazado. La lección 5 ejecuta esa mudanza de datos con detalle (dual-write, backfill, parallel-run); aquí basta ver que la fase 3 la exige y por qué.

Un matiz honesto sobre el ACL de vuelta: solo traduce el formato, no filtra el contenido. Si el servicio evoluciona su comportamiento (calcula un precio distinto, marca un producto de otra forma), el ACL traduce ese resultado nuevo al formato viejo tal cual —no lo "corrige" para que el monolito esté contento—. Formato viejo, comportamiento que puede ser nuevo: esa es la separación que el ACL mantiene. Meter lógica de negocio en el ACL de vuelta escondería comportamiento en el borde y rompería la claridad de "cada mundo, su modelo".

Errores comunes

Construir solo la dirección de entrada del ACL y olvidar la de vuelta. Qué pasa: el equipo escribe to_modern (el servicio ya recibe Product) y da el ACL por hecho —hasta que el monolito recibe la respuesta del servicio y truena, porque le llegó un Product donde esperaba un dict—. Por qué pasa: la dirección de entrada es la visible (hace que el servicio "funcione"); la de vuelta se descubre tarde, cuando el monolito consume la respuesta. Cómo detectarlo: el monolito falla al leer row["desc"] sobre algo que no es un dict, o hay que empezar a parchar consumidores del monolito para que entiendan Product. Cómo corregirlo: el ACL es bidireccional por definición. Escribe to_legacy junto con to_modern y verifica el round-trip. La dirección de vuelta es la que cumple la promesa de no tocar a los cientos de consumidores del monolito; sin ella, extraer obliga a reescribirlos —el big-bang que querías evitar—.

Dejar el servicio leyendo la BD compartida y declararlo "extraído". Qué pasa: el equipo pone el ACL, el servicio ya tiene modelo propio, y —satisfecho— se detiene en la fase 2, con el servicio todavía leyendo shared_db. Por qué pasa: la fase 2 ya "funciona" (el modelo está limpio, el monolito recibe lo suyo), y mover los datos es trabajo extra. Cómo detectarlo: el "servicio" no tiene su propia tabla; consulta las del monolito; un cambio de esquema del monolito lo rompe. Cómo corregirlo: un servicio extraído de verdad duena sus datos —es el único que lee y escribe su store (owned_db)—. La BD compartida es un puente transitorio, no un destino: si se queda, el acoplamiento en la capa de datos sigue vivo y la extracción está a medias. Completa la fase 3: muda los datos y corta la BD compartida.

Filtrar la respuesta del servicio con lógica del monolito en el ACL de vuelta. Qué pasa: el ACL de vuelta, además de traducir el formato, empieza a ajustar la respuesta "para que el monolito esté contento" —redondear un precio, ocultar un campo nuevo—. Por qué pasa: el borde de salida parece el lugar para "acomodar" la respuesta al gusto del viejo. Cómo detectarlo: el ACL de vuelta tiene lógica que no es traducción de formato sino decisión sobre el contenido. Cómo corregirlo: el ACL de vuelta solo traduce el formato modern → legacy, fielmente. Si el servicio produce un comportamiento nuevo, el ACL lo traduce tal cual al formato viejo; no lo "corrige". Las decisiones sobre qué contenido devolver son del dominio del servicio. Meter lógica en el ACL de vuelta esconde comportamiento en el borde y rompe la separación formato/comportamiento que hace claro quién decide qué.

Ejercicios

Ejercicio 1 — Las tripas cambian, el render no. El diario muestra cuatro fases donde el monolito renderiza siempre [1] SSD 1TB - $89.99. (a) ¿Qué cambia, concretamente, entre la fase 1 y la fase 4 por dentro? (b) ¿Qué pieza garantiza que el monolito vea lo mismo pese a esos cambios? (c) ¿Por qué es tan importante que el monolito no note nada?

Ver solución

(a) Entre la fase 1 y la fase 4 cambia todo lo interno del catalog: pasa de ser una función dentro del monolito que devuelve el modelo viejo directo, a ser un servicio autónomo con su propio modelo (Product en vez de dict legacy) y sus propios datos (owned_db en vez de shared_db), consumido por el monolito solo a través del facade. Cambió qué es (función → servicio), cómo modela (dict legacy → Product) y de dónde saca el dato (shared_dbowned_db).

(b) El anti-corruption layer (el ACL bidireccional): el to_modern traduce a la entrada y el to_legacy a la salida, así que sin importar cómo modele o de dónde saque el dato el servicio, lo que cruza hacia el monolito es siempre el dict legacy de siempre. El round-trip en verde (to_legacy(to_modern(row)) == row) certifica que esa traducción de vuelta es fiel.

(c) Porque el monolito tiene cientos de consumidores del catalog (checkout, carrito, emails, admin), todos esperando el modelo viejo. Si el monolito "notara" el cambio —recibiera un Product en vez de un dict—, habría que reescribir esos cientos de consumidores, que es exactamente el big-bang que el método combate. Que el monolito no note nada es lo que permite extraer el catalog sin tocar al resto del monolito: la extracción queda contenida en una rebanada.

Ejercicio 2 — El servicio que no duena sus datos. Un equipo pone el ACL, el servicio ya habla Product, y declara el catalog "extraído" —pero el servicio sigue leyendo shared_db, la tabla del monolito—. (a) ¿En qué fase del diario se quedó? (b) ¿Qué problema deja abierto? (c) ¿Qué le falta para estar extraído de verdad?

Ver solución

(a) Se quedó en la fase 2 (ACL sobre shared_db): el servicio ya tiene modelo limpio y el ACL traduce en las dos direcciones, pero los datos siguen en la BD compartida con el monolito.

(b) Deja abierto el acoplamiento en la capa de datos: mientras el servicio y el monolito compartan la tabla, ninguno es dueño de verdad de esos datos. Un cambio de esquema del monolito rompe al servicio (y viceversa), el servicio no puede evolucionar su almacenamiento libremente, y la frontera que la extracción quería crear no está completa —está en el modelo pero no en los datos—. La BD compartida es un puente transitorio; si se queda, el acoplamiento que se intentaba romper sigue vivo.

(c) Le falta completar la fase 3 (owned_db): mudar los datos del catalog a la BD propia del servicio, donde el servicio sea el único que los lea y escriba, y cortar la BD compartida. La propiedad de los datos —que el servicio duene su store— es lo que completa la extracción. Un servicio con modelo propio pero datos compartidos está disfrazado, no extraído. (La mecánica de mudar esos datos sin downtime es la lección 5.)

Ejercicio 3 — El round-trip roto. Supón que alguien "mejora" el ACL para que to_legacy devuelva prc_cents como entero en vez de string ("es más limpio"). (a) ¿Qué mediría distinto el round-trip? (b) ¿Qué pasaría en el monolito? (c) ¿Cuál es la regla que este cambio viola?

Ver solución

(a) El round-trip to_legacy(to_modern(row)) == row dejaría de dar True: al tomar {"prc_cents": "8999", ...}, traducirlo a Product (price_cents=8999) y de vuelta, to_legacy produciría {"prc_cents": 8999, ...} (entero) en vez de {"prc_cents": "8999", ...} (string). La comparación 8999 == "8999" es False, así que el round-trip marcaría ROTO para cada producto.

(b) El monolito, que hace int(legacy['prc_cents']) esperando un string parseable —o que en otros lugares compara prc_cents como string—, podría romperse o comportarse distinto. Aunque int(8999) funcione, cualquier consumidor del monolito que dependa de que prc_cents sea un string (una comparación, una serialización, un log) se rompería. El "más limpio" cambió el formato que el monolito espera, y el monolito no cambió para acomodarlo.

(c) Viola la regla de que el ACL de vuelta traduce al formato exacto que el monolito espera, fielmente, sin "mejorarlo". El modelo limpio del servicio puede tener price_cents como entero (eso es asunto del servicio), pero al cruzar de vuelta al monolito, el ACL debe entregar prc_cents como string —el formato viejo, tal cual—. El round-trip en verde es justo la prueba de que esa fidelidad se mantiene; romperlo es la señal de que el ACL dejó de cumplir la promesa al monolito.

Resumen y siguiente paso

En esta lección diste el tercer paso del método: extraer el catalog a su propio servicio con un anti-corruption layer (módulo 5). Ejecutaste el diario de extracción en cuatro fases —función interna → servicio con ACL sobre shared_dbowned_db → servicio autónomo— y verificaste la propiedad que hace segura toda la transformación: el monolito renderizó idéntico en las cuatro fases (True) mientras sus tripas cambiaban por completo (qué es, cómo modela, de dónde saca los datos), y el round-trip del ACL volvió fiel para cada producto (True), incluido el inactivo. Viste, con la cocina del restaurante que se muda sin que el comedor lo note, que el ACL es el traductor en la puerta —modelo viejo hacia el monolito, modelo limpio hacia el servicio— y que la dirección de vuelta es la que cumple la promesa de no tocar a los cientos de consumidores del monolito. Y entendiste que la BD compartida es un puente transitorio que se corta: un servicio extraído de verdad duena sus datos.

Antes de avanzar deberías poder: explicar por qué el ACL es bidireccional por definición; describir qué salva la dirección de vuelta (el contrato con los consumidores del monolito); verificar una extracción con el render idéntico por fase y el round-trip; y distinguir un servicio extraído (datos propios) de uno disfrazado (datos compartidos).

La lección 5 da el cuarto paso: migrar los datos de la rebanada (módulo 6). El servicio ya tiene su modelo propio y —en la fase 3— su BD propia, pero mudar los datos del catalog de shared_db a owned_db sin downtime es una técnica en sí misma. Vas a ejecutar el ciclo completo: dual-write + backfill llenan el store nuevo, el parallel-run compara fila por fila y encuentra 2 discrepancias (una fila faltante, una con un campo mal traducido), la reconciliación las arregla, el parallel-run vuelve a correr y da 0, y solo entonces se hace el read-switch. El ACL que pusiste en esta lección es justo lo que hace segura esa mudanza: mantiene el contrato del monolito intacto mientras los datos se mueven por debajo.

Recursos

  • Eric Evans, Domain-Driven Design (Addison-Wesley, 2003), cap. 14, Anti-Corruption Layer — la fuente del patrón: una capa con fachadas y adaptadores que traduce en ambos sentidos entre dos modelos, para que el modelo nuevo no se contamine con el viejo. En inglés.
  • Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 3 — la referencia integral de extraer un servicio de un monolito: el ACL, la propiedad de los datos, y la BD compartida transitoria que se corta. El tercer paso del método sigue su guion. En inglés.
  • Chris Richardson, "Microservice Architecture" — microservices.io. El catálogo de patrones de descomposición, incluyendo la extracción de servicios y el manejo de la base de datos compartida; útil como índice de las piezas que este paso encadena. En inglés.
  • Microsoft, "Anti-corruption Layer pattern" — learn.microsoft.com/azure/architecture/patterns/anti-corruption-layer. La ficha del patrón con el diagrama de la capa que traduce entre los dos subsistemas en ambas direcciones. En inglés.