Saltar a contenido

Process Instance Migration

Migration permite mover una process instance en ejecución de una versión a otra. Implementa 4 behaviors especializados (CatchEvents, Jobs, UserTasks, SequenceFlows). Soporta 18 element types, no soporta compensation/throw/escalation/error events. Requiere mapping explícito source→target. BFS iterativo evita stackoverflow en procesos profundos. Para MVP: clasificada como eliminable — alternativa simple es dejar v1 terminar mientras v2 sirve nuevas instancias.

¿Qué es migration?

Cuando un proceso se modifica y se redeploya como v2, las instancias activas de v1 siguen ejecutándose con v1. Migration permite moverlas a v2 en runtime.

flowchart TD
    V1["Process v1 — Instance #123<br/>(en paso 'task_review')"]
    V2["Process v2 — Instance #123<br/>(continúa en 'task_review_v2')"]
    V1 -->|"MIGRATE command con mapping:<br/>task_review → task_review_v2<br/>task_approve → task_approve_v2"| V2

La instance NO se termina y recrea — su state se reasigna a la nueva process definition.

Componentes especializados

ProcessInstanceMigrationMigrateProcessor coordina 4 behaviors:

Behavior Maneja
ProcessInstanceMigrationCatchEventBehavior Message subscriptions, timers, signals, compensation subscriptions
ProcessInstanceMigrationJobBehavior Jobs activos de service tasks
ProcessInstanceMigrationUserTaskBehavior User tasks pendientes
ProcessInstanceMigrationSequenceFlowBehavior Sequence flows en curso

Cada tipo de elemento BPMN requiere lógica de migration diferente — por eso la separación.

Element types soportados

PROCESS                              SERVICE_TASK
USER_TASK                            SUB_PROCESS
CALL_ACTIVITY                        INTERMEDIATE_CATCH_EVENT
RECEIVE_TASK                         EVENT_SUB_PROCESS
EXCLUSIVE_GATEWAY                    EVENT_BASED_GATEWAY
BUSINESS_RULE_TASK                   SCRIPT_TASK
SEND_TASK                            MULTI_INSTANCE_BODY
PARALLEL_GATEWAY                     INCLUSIVE_GATEWAY
AD_HOC_SUB_PROCESS                   AD_HOC_SUB_PROCESS_INNER_INSTANCE

18 element types. El resto (boundary events, throw events, end events, etc.) son rechazados si están "activos".

Intermediate catch events soportados

Solo 4 tipos: - Message - Timer - Signal - Conditional

Otros (error, escalation, compensation) no son migrables.

Flujo del processor

1. Validate processInstance exists
2. Authorization check (UPDATE_PROCESS_INSTANCE on bpmnProcessId)
3. Validate mapping (no duplicate source IDs)
4. Validate target process definition exists
5. Validate no pending start event subscriptions for target
6. Validate all referred element IDs exist in both processes
7. Build mapping table: source_element_id → target_element_id
8. BFS through all element instances (process + children):
   - For each: tryMigrateElementInstance(...)
     ├── Update process definition key
     ├── Update element ID si hay mapping
     ├── Delegate a behavior según element type
     └── Update children references
9. Emit MIGRATED event

Mapping Instructions

El cliente provee mappings explícitos:

{
  "processInstanceKey": 123,
  "targetProcessDefinitionKey": 456,
  "mappingInstructions": [
    {"sourceElementId": "task_review", "targetElementId": "task_review_v2"},
    {"sourceElementId": "gateway_decision", "targetElementId": "gateway_decision_v2"}
  ]
}

Regla: si un element ID es el mismo en source y target, NO necesita mapping. Solo se mappean elementos con IDs distintos.

BFS iterativo (no recursión)

// avoid stackoverflow using a queue to iterate over the descendants instead of recursion
final var elementInstances = new ArrayDeque<>(List.of(processInstance));
while (!elementInstances.isEmpty()) {
    final var elementInstance = elementInstances.poll();
    tryMigrateElementInstance(...);
    elementInstances.addAll(elementInstanceState.getChildren(elementInstance.getKey()));
}

Decisión clave: procesos pueden tener nesting profundo (sub-process dentro de sub-process dentro de multi-instance). Recursión causaría stackoverflow. ArrayDeque + while loop es safe.

Precondiciones que pueden causar rejection

Del análisis del código:

  1. Process instance no existe: NOT_FOUND
  2. Process instance baneada: rejection
  3. Authorization falla: NOT_FOUND (no FORBIDDEN — opacidad)
  4. Target process definition no existe: NOT_FOUND
  5. Duplicate source element IDs en mapping: INVALID_ARGUMENT
  6. Source o target element ID no existe en sus procesos: INVALID_ARGUMENT
  7. Element type no soportado: INVALID_STATE
  8. Element types source/target incompatibles: INVALID_STATE
  9. Pending message subscriptions del target process: INVALID_STATE
  10. Element está en estado intermedio (ACTIVATING, COMPLETING): INVALID_STATE

Authorization

AuthorizationRequest.builder()
    .resourceType(PROCESS_DEFINITION)
    .permissionType(UPDATE_PROCESS_INSTANCE)
    .tenantId(currentTenant)
    .addResourceId(currentBpmnProcessId)

Permisos necesarios: UPDATE_PROCESS_INSTANCE sobre el bpmnProcessId actual (no el target). Esto refleja que migration es una modificación a una instance existente.

Lo que NO se puede migrar

Cosa Razón
Elementos en estado intermedio (ACTIVATING, COMPLETING) State inconsistente, esperar a estado estable
Boundary events activos con timer corriendo State complejo de migrar
Compensation handlers ejecutándose State complejo
Error events activos No en supported intermediate types
Escalation events No en supported types
Throw events Por definición no esperan
End events Instance ya terminó
Process baneado Process instance broken

Implicaciones para el MVP

Decisión: SKIP migration en MVP

En analysis/mvp-feature-matrix está clasificado como eliminable. Razones:

  1. Implementación muy compleja: 4 behaviors, ~2000 LOC, casos edge difíciles
  2. Casos de uso limitados: solo workflows long-running se benefician
  3. Alternativa válida: dejar v1 terminar, v2 sirve nuevas
  4. Testing complejo: combinaciones de element types × estados explotan

Alternativa simple

flowchart LR
    subgraph V1Lane["v1 (instances cerca de completar)"]
        V1Active[v1 active] --> V1Done[completed]
    end
    subgraph V2Lane["v2 (instances nuevas)"]
        V2Deployed[v2 deployed] --> V2Active[v2 active]
    end
    V1Active -.->|tiempo| V2Deployed

Cuando se deploya v2: - Nuevas instancias van a v2 (latest version) - v1 instances activas terminan en v1 - Eventualmente todas las v1 terminan, se puede limpiar la definition

Esto cubre 80% de casos reales sin código de migration.

Cuándo SÍ implementar migration

  • Workflows long-running (días/semanas)
  • Bugfix urgente que requiere actualizar instances activas
  • Compliance changes con deadline

Estos son casos de empresa grande, no MVP típico.

Si se implementa: subset mínimo

Subset razonable para reducir ~80% complejidad:

SUPPORTED_ELEMENT_TYPES_MVP = {
    SERVICE_TASK,
    USER_TASK,
    EXCLUSIVE_GATEWAY,
    PARALLEL_GATEWAY,
    INTERMEDIATE_CATCH_EVENT  // solo message
}

NO SOPORTAR:
- Sub-processes (explosion de complejidad)
- Multi-instance (otra explosion)
- Boundary events
- Compensation
- Inclusive gateways

Esto cubre service tasks y user tasks en procesos lineales con gateways — el caso 80%.

Lecciones de diseño

  1. BFS iterativo: para cualquier tree traversal, evitar recursión
  2. Behaviors especializados: separar logic por element type
  3. Validación temprana: lista larga de preconditions, fail fast
  4. Mapping explícito: no asumir auto-match — el cliente sabe la intención
  5. Single MIGRATED event: simplifica audit log