Branch by Abstraction
Cambiar implementación conservando integración
Branch by Abstraction permite sustituir gradualmente una implementación haciendo coexistir la anterior y la nueva detrás de una abstracción, mientras la línea principal continúa construyéndose y funcionando. El branching está en la estructura del software: no significa separar durante meses una historia de código en una rama de Git.
Fowler lo presenta como una technique para cambios grandes que permiten seguir entregando durante la transición. El Atlas utiliza kind: practice como clasificación editorial porque su vocabulario no incluye technique; no atribuye a Fowler esa elección de metadata. Branch By Abstraction, 2014.
Fowler atribuye la difusión del término a Paul Hammant, quien reconoce a Stacy Curl. El artículo de Hammant de 2007 describe la alternativa dentro de la línea compartida. Introducing Branch By Abstraction. Esta historia explica el nombre sin convertir la práctica en una política universal sobre toda rama de version control.
Es un mecanismo para transitar entre implementaciones, no un Architecture Profile ni una descripción del estado final. Puede ayudar a evolutionary-architecture; utilizarlo no demuestra por sí solo una trayectoria arquitectónica guiada.
Rama de código y bifurcación por abstracción
Una rama larga de código mantiene separadas historias que después necesitan integrarse. Mientras divergen, un build exitoso de cada historia puede ocultar incompatibilidades semánticas entre sus cambios. Branch by Abstraction mantiene coexistencia explícita en el sistema integrado y reduce la necesidad de reservar toda la sustitución para un merge final.
Esto no impide usar ramas cortas para cambios pequeños. Lo relevante es que el reemplazo completo no dependa de una historia aislada y temporalmente inutilizable. Tampoco basta fusionar código incompleto en main: el camino activo necesita seguir funcionando y el nuevo necesita quedar controlado hasta estar listo.
Integración frecuente ofrece feedback sobre colaboración entre partes. Sigue habiendo trabajo real de compatibilidad, estado y configuración; un merge textual fácil no prueba que dos implementaciones cumplan las mismas obligaciones. testability ayuda a obtener evidencia sobre esos resultados.
La abstracción expresa una responsabilidad
La abstracción debe capturar interacciones relevantes para los consumidores sin filtrar cada detalle del proveedor anterior. interfaces pregunta por contrato y significado; modularity por información que conviene ocultar. Un envoltorio que solo renombra métodos incompatibles puede conservar el mismo coupling.
En Commerce, PaymentGateway podría ofrecer autorización y consulta bajo semántica acordada. Si ambos motores difieren en expiración, confirmación o identificadores, el contrato debe representar las diferencias necesarias o acotar qué casos sustituye primero. Fingir equivalencia universal detrás de execute(anything) desplaza el problema hacia consumidores y errores runtime.
No exige herencia de clases ni programación orientada a objetos. Una función, módulo, protocolo o límite de servicio puede representar el contrato. dependency-structure examina si los callers dependen de él o siguen accediendo directamente al motor antiguo. Dos implementaciones disponibles no bastan si una tercera vía evade la frontera.
Recorrido de sustitución y variantes
Fowler describe introducir la abstracción, mover interacciones hacia ella y reemplazar gradualmente al proveedor, con variantes según qué partes puedan cambiarse separadamente. Los pasos siguientes son una ayuda de razonamiento del Atlas para el ejemplo, no una nueva secuencia normativa atribuida al autor:
- Delimitar responsabilidad y comportamiento que se conserva o cambia.
- Llevar los usos existentes hacia la abstracción con evidencia suficiente.
- Incorporar el reemplazo para una porción comprendida del contrato.
- Elegir qué usos lo activan y observar resultados.
- Retirar la implementación desplazada cuando ya no sea necesaria.
- Revisar si la abstracción sigue siendo una frontera útil.
Puede ser necesario completar más preparación antes de cambiar el primer caller. También puede sustituirse una parte pequeña antes de mover el resto. La condición importante es conservar un sistema funcional, no cumplir un orden artificial ni exigir que todo pueda migrarse en porcentajes arbitrarios.
Commerce: dos motores de pago
Ejemplo propio: Orders solicita autorización a Payments; Inventory mantiene reservas y Shipping espera confirmación válida. Payments quiere cambiar su integración sin trasladar esa autoridad a otros participantes. PaymentGateway representa el contrato dentro de su límite de responsabilidad.
OldPaymentEngine atiende inicialmente los casos existentes. NewPaymentEngine implementa primero autorizaciones de una población cuyo comportamiento está comprendido. Los callers usan PaymentGateway; una selección explícita dirige esos usos al motor pertinente. Orders no decide qué tabla escribir para compensar diferencias entre proveedores.
Refunds históricos todavía pueden necesitar OldPaymentEngine. La selección debería reconocer procedencia de la operación, no solo la configuración actual. Cambiar el default global al motor nuevo no demuestra que pueda atender una devolución de una autorización creada por el anterior.
Antes de ampliar alcance, el equipo contrasta rechazos, respuestas tardías y recuperación. architectural-drivers debe explicar qué mejora se busca; constraints conserva obligaciones que el cambio no puede perder. Mantener entregas durante semanas de transición es valioso solo si esas entregas siguen siendo utilizables.
Selección y comparación segura
La implementación puede elegirse mediante configuración, composición estática, deployment, per-call routing o una feature flag. El toggle es un mecanismo posible, no la definición. El control necesita autoridad, alcance y diagnóstico para reconstruir qué motor procesó un caso concreto.
Ejecutar ambos motores contra el mismo pago real para comparar resultados puede duplicar un efecto externo. Las comparaciones deben distinguir consultas inocuas de autorizaciones, capturas o devoluciones. idempotency depende de un contrato y alcance concretos; una clave compartida no garantiza deduplicación entre dos proveedores independientes.
Usar datos preparados, entornos controlados o comparación de decisiones sin efectos puede aportar evidencia bajo límites declarados. Una diferencia puede ser un defecto o el cambio funcional buscado. La expectativa debe precisar qué significa equivalencia para cada escenario, no asumir que toda salida antigua es correcta.
Estado y autoridad no quedan abstraídos automáticamente
Ambos motores pueden compartir storage, traducir modelos distintos o depender de información separada. La abstracción no sincroniza estado por el hecho de ocultarlo. data-ownership requiere declarar quién es autoritativo ahora, quién escribe y cuándo se transfiere una responsabilidad.
Una opción del ejemplo conserva en Payments el estado de negocio y registra la referencia del proveedor utilizado. Otra puede necesitar mover información histórica. Cada alternativa cambia obligaciones de lectura, conciliación y recuperación; no hay una estrategia universal que se derive del patrón de sustitución.
Dos escrituras independientes siguen expuestas a fallos parciales, orden y reintentos. Cambiar otra vez a OldPaymentEngine no deshace un cobro emitido por NewPaymentEngine. deployment y deployability permiten separar selección reversible de estado y efectos irreversibles.
Además, dos caminos pueden compartir quota, conexiones o base. isolation examina esos límites; coexistencia no garantiza dominios de fallo independientes. Mantener una implementación anterior disponible tampoco demuestra que soporte el formato de datos recién introducido.
Pruebas y evidencia arquitectónica
El contrato puede ejercitarse contra ambas implementaciones con escenarios comunes cuando deben conservar semántica. Agregar casos intencionalmente distintos explicita qué cambia. fitness-functions puede evaluar dependencias prohibidas, comportamiento requerido o criterios de cierre; sus resultados aportan feedback, no ejecutan por sí mismos la transición.
En Commerce interesa que Orders no acceda al motor directamente y que una autorización aceptada no se pierda al modificar selección. Tiempo de confirmación, errores y procedencia deben observarse por implementación. Medir solo que la abstracción devuelve una respuesta rápida puede ocultar trabajo sin completar.
La evidencia tiene límites: tests en un entorno simulado no cubren todas las condiciones del proveedor. También hay costo de mantener fixtures, dobles y combinaciones de configuración. El mecanismo necesita una confianza adecuada al riesgo, no una promesa de equivalencia matemática de sistemas externos.
Relación con otros mecanismos
| Relación | Diferencia que conviene conservar |
|---|---|
| strangler-fig | Desplaza responsabilidad hacia un reemplazo; suele abarcar un sistema o subsistema más amplio. |
| parallel-change | Migra usos entre formas de contrato mientras coexisten. |
| ports-and-adapters | Organiza fronteras y dependencias; un port puede facilitar sustitución sin ser la transición misma. |
La tendencia externo/interno ayuda a comparar Strangler y Branch by Abstraction, pero no es una ley de ubicación. Sus alcances pueden superponerse. La experiencia de Jez Humble también muestra implementaciones con frameworks de persistencia y UI, además de variaciones en selección. Make Large Scale Changes Incrementally, 2011.
Una síntesis propia combina Strangler para mover responsabilidad de Payments, Branch by Abstraction para sustituir su motor y Parallel Change para evolucionar PaymentAuthorized. Pueden componerse; no son sinónimos ni sustituyen una migration-strategy con secuencia, capacidad del equipo y contingencias. refactor-vs-rewrite permite examinar una reimplementación del motor que conserve contratos; su introducción puede seguir integrada en mainline sin crear un proyecto de reemplazo aislado.
Retirar lo desplazado y revisar la frontera
Al completar los usos previstos se retira OldPaymentEngine y su configuración, permisos y dependencias innecesarios, respetando obligaciones históricas. La abstracción puede permanecer si representa una responsabilidad útil, o simplificarse y eliminarse si solo servía a la transición. No debe conservarse indirection automáticamente.
sunsetting ayuda a precisar qué termina: eliminar OldPaymentEngine no implica retirar PaymentGateway ni borrar registros históricos. Puede mantenerse el mismo contrato sobre la implementación nueva. La sustitución técnica y el lifecycle de soporte o disponibilidad tienen alcances que deben declararse por separado.
Humble subraya la importancia de una estrategia de salida y hacer visible el costo de tecnologías coexistentes. En el ejemplo, esa revisión incluye quién mantiene refunds pendientes y qué evidencia permite terminar su soporte; «el motor nuevo está instalado» no basta.
La coexistencia puede introducir technical-debt deliberada y accidental-complexity justificada temporalmente. Debe tener ownership y condición de retiro. Una excepción explícita no equivale automáticamente a architecture-erosion; perder control del estado transitorio puede debilitar intención vigente.
legacy-systems aporta contexto heredado, pero la práctica también sirve en software reciente. systems-thinking obliga a considerar operación y equipos, además de merges. Elegirla requiere comparar preparación, pruebas y carga de coexistencia con el riesgo del cambio concreto. evolution-and-modernization conecta esa decisión con las otras preguntas de evolución.