Pular para conteúdo

MERGE - Unir Caminhos

O que é este Node?

O MERGE é o node responsável por unir múltiplos caminhos de execução em um único ponto, permitindo sincronizar fluxos paralelos antes de continuar (equivalente ao merge point em diagramas de fluxo).

Por que este Node existe?

Flows complexos frequentemente dividem execução em múltiplos caminhos paralelos ou condicionais. O MERGE existe para:

  1. Sincronizar caminhos paralelos: Aguardar múltiplas branches completarem
  2. Convergir fluxos condicionais: Unir diferentes IF/ELSE em um ponto comum
  3. Processar resultados agregados: Combinar dados de múltiplas fontes
  4. Evitar duplicação de nodes: Não repetir mesma lógica em cada branch
  5. Controlar estratégias: all (aguarda todos), first (primeiro que chegar), any (qualquer um)

Como funciona internamente?

Quando o MERGE é executado, o sistema:

  1. Identifica sourcePaths: Lista de node IDs que convergem neste ponto
  2. Aplica mergeStrategy: Define comportamento de sincronização
  3. Aguarda condição: Baseado na estratégia escolhida
  4. Coleta variáveis: Combina context de todos os paths
  5. Continua execução: Após condição satisfeita

Código interno (logic-control-executor.service.ts:137-149):

private async executeMerge(parameters: any, context: any): Promise<any> {
  const { sourcePaths, mergeStrategy } = parameters;

  this.logger.log(`🔀 MERGE - Merging ${sourcePaths?.length || 0} paths`);

  return {
    success: true,
    action: 'paths_merged',
    sourcePaths: sourcePaths || [],
    mergeStrategy: mergeStrategy || 'all',
    timestamp: new Date().toISOString()
  };
}

Quando você DEVE usar este Node?

Use MERGE quando precisar convergir múltiplos caminhos:

Casos de uso

  1. Sincronizar branches paralelas: Aguardar todas API calls completarem
  2. Convergir IF/ELSE: Unir caminhos após CONDITION
  3. Processar múltiplos SWITCH cases: Convergir após diferentes ações
  4. Combinar resultados: Agregar dados de múltiplas fontes
  5. Race condition: Continuar com primeiro path que completar

Quando NÃO usar MERGE

  • Flow linear simples: Se não há caminhos paralelos ou condicionais, MERGE é desnecessário
  • Apenas um caminho ativo: Use MERGE só quando realmente há múltiplos paths convergindo
  • Logic duplicada é aceitável: Se repetir nodes em cada branch é mais simples, não force uso de MERGE

Parâmetros

Campo Tipo Obrigatório Descrição
sourcePaths array Sim Array de node IDs que convergem neste MERGE
mergeStrategy string Não all, first, any (padrão: all)

Parâmetros Detalhados

sourcePaths (array, obrigatório)

O que é: Lista de IDs dos nodes que devem convergir neste ponto de merge.

Flow completo para testar:

{
  "name": "Teste MERGE - SourcePaths",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 200 },
      "data": { "label": "Início" }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 300, "y": 200 },
      "data": {
        "label": "É VIP?",
        "parameters": {
          "condition": "{{tipo}} == 'vip'",
          "trueAction": "message_vip",
          "falseAction": "message_normal"
        }
      }
    },
    {
      "id": "message_vip",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Mensagem VIP",
        "parameters": {
          "message": "⭐ Bem-vindo cliente VIP!"
        }
      }
    },
    {
      "id": "message_normal",
      "type": "message",
      "position": { "x": 500, "y": 300 },
      "data": {
        "label": "Mensagem Normal",
        "parameters": {
          "message": "👋 Bem-vindo!"
        }
      }
    },
    {
      "id": "merge_1",
      "type": "merge",
      "position": { "x": 700, "y": 200 },
      "data": {
        "label": "Convergir Caminhos",
        "parameters": {
          "sourcePaths": ["message_vip", "message_normal"],
          "mergeStrategy": "all"
        }
      }
    },
    {
      "id": "message_final",
      "type": "message",
      "position": { "x": 900, "y": 200 },
      "data": {
        "label": "Mensagem Final",
        "parameters": {
          "message": "📋 Como posso ajudar?"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 200 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "condition_1" },
    { "source": "condition_1", "target": "message_vip", "condition": "true" },
    { "source": "condition_1", "target": "message_normal", "condition": "false" },
    { "source": "message_vip", "target": "merge_1" },
    { "source": "message_normal", "target": "merge_1" },
    { "source": "merge_1", "target": "message_final" },
    { "source": "message_final", "target": "end_1" }
  ]
}

Teste: Define variável tipo como "vip" ou "normal". Independente do caminho escolhido, MERGE aguarda e continua para "Como posso ajudar?"

mergeStrategy (string, opcional)

O que é: Estratégia de sincronização dos caminhos.

Padrão: "all" (aguarda todos os caminhos)

Estratégias disponíveis: - "all": Aguarda TODOS os sourcePaths completarem (AND) - "first": Continua com PRIMEIRO path que completar (race) - "any": Continua se QUALQUER path completar (OR)

Flow completo para testar:

{
  "name": "Teste MERGE - MergeStrategy First",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 200 },
      "data": { "label": "Início" }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 300, "y": 200 },
      "data": {
        "label": "Iniciar Busca",
        "parameters": {
          "message": "🔍 Buscando em múltiplas fontes..."
        }
      }
    },
    {
      "id": "delay_1",
      "type": "delay",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "API Lenta (5s)",
        "parameters": {
          "duration": 5,
          "unit": "seconds"
        }
      }
    },
    {
      "id": "delay_2",
      "type": "delay",
      "position": { "x": 500, "y": 200 },
      "data": {
        "label": "Cache Rápido (1s)",
        "parameters": {
          "duration": 1,
          "unit": "seconds"
        }
      }
    },
    {
      "id": "delay_3",
      "type": "delay",
      "position": { "x": 500, "y": 300 },
      "data": {
        "label": "DB Médio (3s)",
        "parameters": {
          "duration": 3,
          "unit": "seconds"
        }
      }
    },
    {
      "id": "merge_1",
      "type": "merge",
      "position": { "x": 700, "y": 200 },
      "data": {
        "label": "Primeiro que Responder",
        "parameters": {
          "sourcePaths": ["delay_1", "delay_2", "delay_3"],
          "mergeStrategy": "first"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 900, "y": 200 },
      "data": {
        "label": "Resultado",
        "parameters": {
          "message": "✅ Resposta mais rápida recebida!"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 200 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "message_1" },
    { "source": "message_1", "target": "delay_1" },
    { "source": "message_1", "target": "delay_2" },
    { "source": "message_1", "target": "delay_3" },
    { "source": "delay_1", "target": "merge_1" },
    { "source": "delay_2", "target": "merge_1" },
    { "source": "delay_3", "target": "merge_1" },
    { "source": "merge_1", "target": "message_2" },
    { "source": "message_2", "target": "end_1" }
  ]
}

Teste: Com strategy="first", continua após 1s (cache rápido) sem aguardar API lenta (5s) ou DB (3s)!

Exemplo 1: Convergir IF/ELSE

Objetivo: Unir caminhos de desconto VIP/Normal em ponto comum

{
  "name": "MERGE - Convergir Desconto",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 200 },
      "data": { "label": "Início" }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 300, "y": 200 },
      "data": {
        "label": "Definir Preço",
        "parameters": {
          "name": "preco",
          "value": 100,
          "operation": "set"
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 500, "y": 200 },
      "data": {
        "label": "É VIP?",
        "parameters": {
          "condition": "{{vip}} == true",
          "trueAction": "accumulator_vip",
          "falseAction": "accumulator_normal"
        }
      }
    },
    {
      "id": "accumulator_vip",
      "type": "accumulator",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Desconto 20%",
        "parameters": {
          "name": "preco",
          "value": 20,
          "operation": "subtract"
        }
      }
    },
    {
      "id": "message_vip",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Info VIP",
        "parameters": {
          "message": "⭐ Desconto VIP aplicado!"
        }
      }
    },
    {
      "id": "accumulator_normal",
      "type": "accumulator",
      "position": { "x": 700, "y": 300 },
      "data": {
        "label": "Desconto 5%",
        "parameters": {
          "name": "preco",
          "value": 5,
          "operation": "subtract"
        }
      }
    },
    {
      "id": "message_normal",
      "type": "message",
      "position": { "x": 900, "y": 300 },
      "data": {
        "label": "Info Normal",
        "parameters": {
          "message": "🎟️ Desconto padrão aplicado!"
        }
      }
    },
    {
      "id": "merge_1",
      "type": "merge",
      "position": { "x": 1100, "y": 200 },
      "data": {
        "label": "Unir Caminhos",
        "parameters": {
          "sourcePaths": ["message_vip", "message_normal"],
          "mergeStrategy": "all"
        }
      }
    },
    {
      "id": "message_final",
      "type": "message",
      "position": { "x": 1300, "y": 200 },
      "data": {
        "label": "Total Final",
        "parameters": {
          "message": "💰 Total com desconto: R$ {{preco}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1500, "y": 200 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "variable_1" },
    { "source": "variable_1", "target": "condition_1" },
    { "source": "condition_1", "target": "accumulator_vip", "condition": "true" },
    { "source": "condition_1", "target": "accumulator_normal", "condition": "false" },
    { "source": "accumulator_vip", "target": "message_vip" },
    { "source": "accumulator_normal", "target": "message_normal" },
    { "source": "message_vip", "target": "merge_1" },
    { "source": "message_normal", "target": "merge_1" },
    { "source": "merge_1", "target": "message_final" },
    { "source": "message_final", "target": "end_1" }
  ]
}

Saída esperada (se vip=true):

Sistema: ⭐ Desconto VIP aplicado!
Sistema: 💰 Total com desconto: R$ 80

Saída esperada (se vip=false):

Sistema: 🎟️ Desconto padrão aplicado!
Sistema: 💰 Total com desconto: R$ 95

Exemplo 2: Race Condition - Múltiplas APIs

Objetivo: Consultar 3 APIs e usar a resposta mais rápida

{
  "name": "MERGE - Race Condition APIs",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 300 },
      "data": { "label": "Início" }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 300, "y": 300 },
      "data": {
        "label": "Iniciar",
        "parameters": {
          "message": "🔍 Consultando cotação em múltiplas fontes..."
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "API 1 - Binance",
        "parameters": {
          "name": "fonte",
          "value": "Binance",
          "operation": "set"
        }
      }
    },
    {
      "id": "delay_1",
      "type": "delay",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Simular Latência 2s",
        "parameters": {
          "duration": 2,
          "unit": "seconds"
        }
      }
    },
    {
      "id": "variable_2",
      "type": "variable",
      "position": { "x": 500, "y": 300 },
      "data": {
        "label": "API 2 - Coinbase",
        "parameters": {
          "name": "fonte",
          "value": "Coinbase",
          "operation": "set"
        }
      }
    },
    {
      "id": "delay_2",
      "type": "delay",
      "position": { "x": 700, "y": 300 },
      "data": {
        "label": "Simular Latência 1s",
        "parameters": {
          "duration": 1,
          "unit": "seconds"
        }
      }
    },
    {
      "id": "variable_3",
      "type": "variable",
      "position": { "x": 500, "y": 500 },
      "data": {
        "label": "API 3 - Kraken",
        "parameters": {
          "name": "fonte",
          "value": "Kraken",
          "operation": "set"
        }
      }
    },
    {
      "id": "delay_3",
      "type": "delay",
      "position": { "x": 700, "y": 500 },
      "data": {
        "label": "Simular Latência 3s",
        "parameters": {
          "duration": 3,
          "unit": "seconds"
        }
      }
    },
    {
      "id": "merge_1",
      "type": "merge",
      "position": { "x": 900, "y": 300 },
      "data": {
        "label": "Primeira Resposta",
        "parameters": {
          "sourcePaths": ["delay_1", "delay_2", "delay_3"],
          "mergeStrategy": "first"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 1100, "y": 300 },
      "data": {
        "label": "Resultado",
        "parameters": {
          "message": "✅ Cotação recebida de {{fonte}} (mais rápida)!"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1300, "y": 300 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "message_1" },
    { "source": "message_1", "target": "variable_1" },
    { "source": "message_1", "target": "variable_2" },
    { "source": "message_1", "target": "variable_3" },
    { "source": "variable_1", "target": "delay_1" },
    { "source": "variable_2", "target": "delay_2" },
    { "source": "variable_3", "target": "delay_3" },
    { "source": "delay_1", "target": "merge_1" },
    { "source": "delay_2", "target": "merge_1" },
    { "source": "delay_3", "target": "merge_1" },
    { "source": "merge_1", "target": "message_2" },
    { "source": "message_2", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: 🔍 Consultando cotação em múltiplas fontes...
[aguarda 1 segundo]
Sistema: ✅ Cotação recebida de Coinbase (mais rápida)!

Resposta do Node

{
  "success": true,
  "action": "paths_merged",
  "sourcePaths": ["message_vip", "message_normal"],
  "mergeStrategy": "all",
  "timestamp": "2025-01-15T10:30:00.000Z"
}

Estratégias de Merge

Strategy Comportamento Quando Usar
all Aguarda TODOS os paths completarem Sincronização completa, agregar múltiplos resultados
first Continua com PRIMEIRO que completar Race condition, otimizar latência, fallback rápido
any Continua se QUALQUER completar (similar ao first) Validação alternativa, múltiplas fontes

Boas Práticas

SIM:

  • Use "all" quando precisar de dados de todos os paths
  • Use "first" para otimizar latência (pegar resposta mais rápida)
  • Liste todos os node IDs em sourcePaths
  • Use MERGE após CONDITION/SWITCH para convergir
  • Combine variáveis de diferentes paths após merge

NÃO:

  • Não omita sourcePaths obrigatório
  • Não esqueça de conectar todos os paths ao MERGE
  • Não use "first" se precisar de todos os resultados
  • Não crie loops infinitos (path voltando para si mesmo)

Dicas

💡 All strategy: Útil para operações paralelas que devem completar (múltiplas API calls necessárias) 💡 First strategy: Perfeito para fallback de APIs (usa primeira que responder) 💡 Debugging: Use MESSAGE antes de cada path para rastrear qual executou 💡 Performance: "first" pode economizar tempo ignorando paths lentos 💡 Variables: Após merge, variáveis de todos os paths estão disponíveis no context

Próximo Node

SCHEDULE - Agendar execuções futuras → CONDITION - Criar branches para depois fazer merge → SWITCH - Múltiplos paths para convergir