Parallel Change / Expand and Contract

Evolucionar un contrato sin cambiar a todos a la vez

Parallel Change permite introducir un cambio incompatible mediante una transición en la que conviven temporalmente las formas anterior y nueva, se migran sus usuarios y después se retira la forma desplazada. El objetivo es desacoplar movimientos de proveedores y consumidores que, de otro modo, tendrían que cambiar coordinadamente.

Danilo Sato lo define como un pattern, también conocido como Expand and Contract, y presenta las fases expand, migrate y contract. En sus notas también utiliza technique y reconoce su documentación previa como refactoring strategy por Joshua Kerievsky. kind: pattern sigue la formulación principal; no declara una clasificación universal excluyente. Sato, Parallel Change, 2014.

Puede aplicarse a interfaces de código, APIs, eventos y schemas. No prescribe una arquitectura final. Una transición de contrato puede ser parte de evolutionary-architecture, aunque usarla no prueba por sí solo orientación arquitectónica, feedback suficiente ni resultado favorable.

Expand, migrate y contract

Estas fases proceden de Sato, no son una taxonomía inventada por el Atlas:

FasePapel en la transición
ExpandHabilitar la forma nueva conservando soporte de la anterior.
MigrateTrasladar gradualmente los usos al contrato nuevo.
ContractRetirar la forma anterior cuando ya no sea necesaria.

En el ejemplo propio de Commerce, distinguir qué admite el proveedor de qué utiliza realmente cada consumidor evita declarar una fase completa solo porque existe código nuevo. Una versión instalada puede conservar un job que consulta el campo antiguo una vez al mes.

La ventana de compatibilidad es el período de soporte transitorio acordado. Necesita alcance, poblaciones, responsable y condición de salida. El calendario puede coordinar equipos, pero no demuestra que haya desaparecido el uso. Tampoco corresponde borrar una representación deliberadamente soportada por obligaciones vigentes para cumplir una fecha arbitraria.

Compatibilidad: dirección y significado

Conviene nombrar combinaciones concretas antes que decir «compatible» sin contexto. ¿El consumidor anterior procesa respuestas del proveedor nuevo? ¿El consumidor nuevo puede trabajar con el proveedor anterior mientras dura un rollout? Las dos preguntas tienen direcciones y requisitos diferentes.

En una API, conservar a los clientes existentes al evolucionar el proveedor se describe como backward compatibility; tolerar futuras extensiones del proveedor es una preocupación de forward compatibility del cliente. Para schemas de eventos hay que precisar también lector y escritor: Confluent denomina backward a que el lector nuevo lea datos antiguos, y forward a que el lector antiguo lea datos nuevos. Schema Evolution and Compatibility. No trasladar una etiqueta entre contextos sin declarar la combinación que debe funcionar.

Coexistencia significa que las variantes pueden operar durante el período declarado; no exige schemas idénticos. Agregar un campo puede romper un consumidor estricto, alterar tamaño o introducir significado inesperado. interfaces necesita declarar obligaciones, opcionalidad y manejo de valores desconocidos. interoperability comprueba interpretación, además de transmisión.

Commerce: evolucionar PaymentAuthorized

Ejemplo propio: Orders y un consumidor de Shipping reciben un evento con payment_status: authorized. Payments quiere introducir authorization_state: approved, con correspondencia explícita solo para los estados soportados. No se presupone que cualquier cambio de nombre sea semánticamente inocuo.

Durante Expand, Payments puede ofrecer ambos campos derivados de una misma decisión autoritativa, bajo el contrato acordado. Primero se comprueba que los consumidores existentes toleran la ampliación. Si no la toleran, puede necesitarse un canal o endpoint separado; eso cambia el mecanismo, no elimina la transición.

Durante Migrate, cada consumidor adopta el campo nuevo y se verifica su comportamiento. Inventory conserva sus reservas y Shipping sigue requiriendo confirmación válida. Orders no empieza a calcular por su cuenta una autorización distinta. data-ownership mantiene a Payments como autoridad sobre ese hecho.

Durante Contract, se retira el campo antiguo únicamente cuando su uso relevante terminó. Mensajes retenidos, replays y clientes desconectados también pueden pertenecer al alcance soportado. Una lectura histórica puede necesitar un traductor aun después de que todos los productores activos emitan la forma nueva.

Qué se demuestra al retirar una forma

Un dashboard con cero llamadas recientes no demuestra ausencia universal de consumidores. Puede faltar instrumentación, una población estar inactiva o un reporte ejecutarse al cierre mensual. legacy-systems ayuda a buscar lectores fuera del código que el equipo controla.

En Commerce, payment_status parece no tener usos hasta descubrir que BI consulta directamente la columna. Borrarla después del deployment de Orders rompe una obligación externa. La solución necesita acordar contrato y migración de ese consumidor, no redefinirlo como inexistente.

Inventario de consumidores, logs, trazas, contratos ejercitados, versiones instaladas y ventanas representativas ofrecen evidencias complementarias. El propietario del contrato necesita acordar qué conjunto basta y qué incertidumbre residual acepta. systems-thinking amplía la observación hacia informes, operación y otros equipos afectados.

Ejemplo de schema y datos persistidos

data-migration trata movimiento o transformación de registros y la autoridad aplicable en cada estado. Parallel Change puede sostener formas compatibles durante ese movimiento, pero no ejecuta por sí solo backfill, reconciliación ni cutover de escritores. Tener ambas representaciones disponibles tampoco demuestra que sus consumidores estén migrados.

La literatura de Evolutionary Database Design describe un período de transición con accesos antiguos y nuevos, y advierte sobre aplicaciones y reportes que comparten información. Sadalage y Fowler, Evolutionary Database Design, sección Transition phase. Eso respalda aplicar la idea más allá de una firma de función.

En otro ejemplo propio, Payments cambia provider_reference por processor_reference conservando exactamente la semántica acordada. Añadir una columna nullable permite preparar soporte, pero los registros existentes y escrituras concurrentes todavía necesitan tratamiento. Si el objetivo cambiara significado, una copia de texto no resolvería la transformación.

Un backfill debe distinguir valores históricos de escrituras más recientes para no sobrescribir información válida. Un lector que consulta primero lo nuevo y después lo viejo necesita reglas sobre ausencia, precedencia y discrepancias. La secuencia de escritores, lectores, verificación y retiro depende de esas condiciones; no se ofrece una receta universal.

Expand tampoco garantiza ausencia de interrupción. DDL, índices, validaciones y volumen tienen comportamiento propio del motor. PostgreSQL, por ejemplo, documenta diferencias según operación y expresión de default. PostgreSQL 18, Modifying Tables. La técnica de transición no sustituye comprobar el cambio real en el entorno pertinente.

Dual writes, repetición y reconciliación

Mantener dos representaciones puede hacerse mediante traducción, una fuente común o escrituras coordinadas; no exige dos commits independientes. Si una escritura antigua tiene éxito y la nueva falla, hace falta detectar y resolver la divergencia. Orden, reintentos y backfill pueden generar otras inconsistencias.

transactional-outbox puede relacionar cambio local con publicación; idempotency puede limitar efectos repetidos. Ninguno hace atómicas por definición dos escrituras en stores distintos. La decisión debe especificar autoridad, reparación y evidencia de completitud antes de retirar el acceso anterior.

coordination mantiene visible quién mueve el proceso, mientras dependency-structure permite localizar quién sigue ligado a la representación antigua. Un adapter puede contener propagación y seguir dependiendo internamente del schema que se pretende abandonar. Ese uso necesita aparecer en el criterio de cierre.

Versioning, blue/green y feature flags

Versioning puede sostener variantes indefinidamente. Parallel Change dirige una transición con retirada de la forma anterior. Publicar v2 sin migrar usuarios ni decidir el destino de v1 no completa ese recorrido. Ambos mecanismos pueden colaborar sin volverse equivalentes.

Blue/green gestiona entornos y selección de tráfico; no define automáticamente cómo coexisten los contratos de datos. Puede participar en una aplicación de Parallel Change, como señala Sato, pero cambiar tráfico entre dos entornos no demuestra que sus lectores entiendan el mismo estado. deployment y deployability conservan esa pregunta.

Una feature flag puede controlar qué forma utiliza una población. No crea compatibilidad por sí sola ni prueba que sea seguro desactivarla después de escribir información nueva. Rollback del executable no equivale a restaurar schema, mensajes o efectos previos.

Comparar y componer mecanismos

MecanismoPregunta principal
strangler-fig¿Cómo desplazar responsabilidad hacia una capacidad de reemplazo?
Parallel Change¿Cómo trasladar usos entre formas de contrato?
branch-by-abstraction¿Cómo sustituir implementación detrás de una abstracción?

Una migración de Payments puede usar los tres: routing hacia una capacidad nueva, dos motores detrás de PaymentGateway y evolución del evento para consumidores existentes. Esa composición es una síntesis del Atlas; no obliga a aplicar los tres ni sustituye una migration-strategy que ordene dependencias, inversión y contingencias. El mecanismo resuelve coexistencia de formas, no selecciona por sí solo el alcance y secuencia de toda la transformación.

Evidencia, deuda y finalización

fitness-functions puede contrastar versiones soportadas, correspondencia semántica, completitud del backfill o desaparición de una dependencia. Las propiedades que preserva la transición deben responder a constraints y architectural-drivers: pagos aceptados no perdidos, consumidores vigentes atendidos y autoridad explícita. Los checks aportan evidencia; no ejecutan la migración por sí mismos.

Soportar ambas formas cuesta almacenamiento, pruebas y comprensión. Puede ser technical-debt deliberada con responsable y condición de retiro. Contract pertenece a la transición, no es pulido opcional después de declararla terminada. Si el soporte anterior se conserva intencionalmente, hay que revisar alcance y costo en lugar de fingir que desapareció.

deprecation puede comunicar qué forma deja de ser preferida mientras sigue utilizable. sunsetting incorpora el lifecycle de retirada, incluyendo soporte y datos que Contract no resuelve por sí solo. Una fecha anunciada no demuestra que haya terminado la migración de consumidores ni que desaparezcan sus obligaciones históricas.

Una excepción temporal puede ser evolución legítima. Si pierde ownership y sus caminos se perpetúan sin control, puede contribuir a architecture-erosion. testability y observación ayudan a detectar ese estado; pasar checks parciales no demuestra ausencia de consumidores ocultos. evolution-and-modernization conecta estas preguntas con el cambio más amplio.