7.6 KiB
Arquitectura y evolución de sistemas heredados (Legacy)
Referencia especializada del skill
rdx-solid. Consulta arquitectura.md y el documento del ámbito afectado: arquitectura-backend.md, arquitectura-frontend.md o arquitectura-shared.md.
1. Propósito
Modernizar y mantener sistemas existentes con seguridad, preservando comportamientos y contratos requeridos. «Legacy» no significa necesariamente código de baja calidad: puede ser software estable, valioso y escasamente documentado.
Prioridad: comprender antes de cambiar; verificar antes de declarar el cambio correcto. No imponer patrones o nomenclaturas nuevas de forma masiva.
2. Reconocimiento inicial
- Identifica límites del sistema, ejecutables, servicios y consumidores.
- Revisa dependencias, versiones, compilación y despliegue.
- Localiza contratos públicos, formatos de intercambio y persistencia.
- Identifica comportamientos implícitos, trabajos programados y efectos secundarios.
- Comprueba las pruebas existentes y los mecanismos de recuperación.
- Delimita la tarea, sus riesgos y los componentes afectados.
No presupongas que un comportamiento aparentemente extraño es un error: puede ser un requisito histórico o un contrato con consumidores externos.
3. Caracterización del comportamiento
Cuando falten especificaciones o pruebas, utiliza ejemplos de entrada y salida, datos anonimizados, consultas, trazas o pruebas de caracterización para describir el comportamiento actual.
Distingue:
- Comportamiento a conservar: contratos y resultados vigentes necesarios.
- Defecto a corregir: fallo confirmado respecto al comportamiento esperado.
- Cambio funcional: nuevo requisito que modifica la semántica.
No conviertas automáticamente un fallo conocido en una obligación permanente mediante una prueba de caracterización.
4. Refactorización incremental
- Realiza cambios pequeños y reversibles cuando sea posible.
- Separa las correcciones funcionales de las refactorizaciones generales.
- Introduce pruebas de regresión en torno a comportamientos críticos cuando sea viable.
- No renombres, reformatees o muevas grandes áreas ajenas a la tarea.
- Conserva convenciones y límites de módulos existentes salvo necesidad justificada.
- Utiliza adaptadores temporales si permiten migrar consumidores gradualmente.
- Evita reescrituras completas sin evaluación de costes, riesgos y continuidad del servicio.
La ausencia de pruebas no prohíbe todo cambio: exige mecanismos de verificación proporcionales, mayor cautela y documentación de incertidumbres.
5. Estrategias de modernización
| Estrategia | Cuándo considerarla | Riesgo a controlar |
|---|---|---|
| Refactorización localizada | Corregir estructura sin cambiar contratos | Regresiones involuntarias |
| Adaptador / fachada | Aislar un contrato antiguo | Traducción semántica incorrecta |
| Capa anticorrupción | Separar modelos de dominio antiguo y nuevo | Complejidad de mapeo |
| Strangler Fig | Sustituir capacidades progresivamente | Enrutamiento y coexistencia |
| Migración por etapas | Cambiar persistencia o infraestructura | Consistencia y reversión |
| Sustitución completa | Sistema realmente inviable de mantener | Alcance, datos y paridad funcional |
Escoge la opción con menor riesgo y coste total razonable; no asumas que reescribir equivale a mejorar.
6. Capas de adaptación y anticorrupción
Una capa anticorrupción evita que el nuevo dominio adopte conceptos o errores del modelo antiguo.
interface CustomerDirectory {
findCustomer(id: CustomerId): Promise<Customer | null>;
}
class LegacyCustomerAdapter implements CustomerDirectory {
constructor(private readonly legacyApi: LegacyCustomerApi) {}
async findCustomer(id: CustomerId): Promise<Customer | null> {
const legacyRecord = await this.legacyApi.readCustomer(id.value);
return legacyRecord ? mapLegacyCustomer(legacyRecord) : null;
}
}
Los tipos y colaboradores son ilustrativos. Verifica diferencias de identificación, nulos, fechas, moneda, errores y codificación: un mapeo estructural no garantiza equivalencia semántica.
No crees una capa anticorrupción cuando un adaptador sencillo sea suficiente.
7. Migración de datos y persistencia
- Determina esquemas, claves, restricciones, volumen y dependencias.
- Diseña migraciones reproducibles con validaciones de integridad.
- Evalúa ventanas de mantenimiento, backups y recuperación.
- Considera expansión-contracción para cambios de esquema compatibles.
- Si existe doble escritura, define fallos, reconciliación y fuente de verdad; no la adoptes sin necesidad.
- Verifica volumen, rendimiento y compatibilidad con versiones desplegadas.
- Evita transformaciones destructivas sin un procedimiento probado o adecuado de recuperación.
8. Contratos, compatibilidad y despliegue
Identifica qué consumidores pueden quedar afectados: clientes externos, informes, procesos por lotes, integraciones, archivos de intercambio, aplicaciones móviles o servicios anteriores.
- Mantén formatos y significado de campos cuando sea requerido.
- Utiliza versionado o adaptadores si los consumidores no pueden migrar a la vez.
- Controla coexistencia de versiones y despliegues escalonados.
- Define condiciones de reversión cuando el riesgo lo exija.
- No elimines endpoints o campos aparentemente no usados sin verificar consumidores externos.
9. Dependencias y tecnologías obsoletas
- Determina compatibilidad y riesgos de seguridad de runtimes, librerías y drivers.
- No actualices todas las dependencias durante una corrección puntual sin necesidad.
- Prioriza vulnerabilidades o incompatibilidades relevantes según su impacto.
- Realiza actualizaciones por etapas y verifica sus contratos.
- Documenta dependencias no sustituibles y mecanismos de mitigación.
10. Pruebas y seguimiento
- Pruebas de caracterización para comportamiento existente.
- Regresión para defectos corregidos.
- Integración para adaptadores y persistencia.
- Contrato para consumidores externos y coexistencia.
- E2E para flujos de negocio críticos.
- Comparación controlada de resultados antiguo/nuevo cuando sea viable.
Evita utilizar exclusivamente métricas de cobertura como garantía de migración. Observa errores, tiempos de respuesta, integridad de datos y resultados funcionales tras un cambio cuando el entorno lo permita.
11. Señales de alerta
- Reescribir todo antes de conocer el comportamiento existente.
- Confundir limpieza estética con mejora arquitectónica necesaria.
- Modificar contratos sin inventariar consumidores.
- Migrar datos sin validar integridad ni recuperación.
- Repartir reglas inconsistentes entre implementación antigua y nueva.
- Eliminar lógica aparentemente redundante con efectos históricos desconocidos.
- Introducir frameworks nuevos para una corrección localizada.
- Declarar paridad funcional sin pruebas o evidencias suficientes.
12. Lista de comprobación
- ¿Conozco comportamiento, contratos y consumidores afectados?
- ¿He distinguido defectos de comportamientos históricos a conservar?
- ¿La estrategia de migración es proporcional al riesgo?
- ¿Existe verificación antes y después del cambio?
- ¿La integridad de los datos y la compatibilidad están protegidas?
- ¿Se han previsto reversión o recuperación cuando corresponda?
- ¿He evitado reestructuraciones ajenas a la tarea?
- ¿He comunicado incertidumbres y verificaciones pendientes?
Principio final: modernizar significa reducir el riesgo y mejorar la capacidad de evolución, no reescribir por preferencia tecnológica.