Pular para conteúdo

ELASTICSEARCH - Search and Analytics Engine

O que é este Node?

O ELASTICSEARCH é o node responsável por integrar com Elasticsearch para indexar documentos, buscar dados, atualizar/deletar documentos e gerenciar índices.

Por que este Node existe?

Busca e análise de grandes volumes de dados são desafios comuns. O ELASTICSEARCH existe para:

  1. Busca full-text: Pesquisar texto em milhões de documentos em milissegundos
  2. Analytics em tempo real: Analisar logs e métricas instantaneamente
  3. Busca fuzzy: Encontrar resultados mesmo com typos e variações
  4. Agregações: Calcular estatísticas e métricas sobre dados
  5. Escalabilidade: Distribuir dados em múltiplos nodes automaticamente

Como funciona internamente?

Quando o ELASTICSEARCH é executado, o sistema:

  1. Identifica operação: index_document, search, get_document, update_document, delete_document, create_index, delete_index
  2. Valida parâmetros: URL do ES, índice, documento conforme operação
  3. Configura autenticação: API Key se fornecido
  4. Executa operação HTTP: POST/GET/DELETE no Elasticsearch REST API
  5. Retorna resultado: Dados encontrados ou confirmação da operação
  6. Se erro: Lança exceção com detalhes

Código interno (infrastructure-executor.service.ts:139-169):

private async executeElasticsearch(parameters: any, context: any): Promise<any> {
  const { operation, index, document, query, url, apiKey } = parameters;

  this.logger.log(`🔍 ELASTICSEARCH - Operation: ${operation}, Index: ${index}`);

  switch (operation) {
    case 'index_document':
      return this.indexDocument(index, document, url, apiKey, context);

    case 'search':
      return this.searchDocuments(index, query, url, apiKey, context);

    case 'get_document':
      return this.getDocument(index, parameters.id, url, apiKey, context);

    case 'update_document':
      return this.updateDocument(index, parameters.id, document, url, apiKey, context);

    case 'delete_document':
      return this.deleteDocument(index, parameters.id, url, apiKey, context);

    case 'create_index':
      return this.createIndex(index, parameters.mapping, url, apiKey, context);

    case 'delete_index':
      return this.deleteIndex(index, url, apiKey, context);

    default:
      throw new Error(`Unsupported Elasticsearch operation: ${operation}`);
  }
}

Quando você DEVE usar este Node?

Use ELASTICSEARCH quando precisar busca e analytics avançados:

Casos de uso

  1. Busca em produtos: E-commerce com filtros, categorias e busca fuzzy
  2. Log aggregation: Centralizar e buscar logs (stack ELK)
  3. Monitoramento: Buscar e analisar métricas de aplicação
  4. Busca em documentos: Pesquisar em PDFs, artigos, documentação
  5. Autocomplete: Sugestões de busca em tempo real

Quando NÃO usar ELASTICSEARCH

  • Database primário: Use PostgreSQL/MySQL para dados transacionais
  • Cache: Use Redis para cache simples
  • Filas: Use RabbitMQ/Kafka para message queues
  • Dados pequenos: Overhead desnecessário para <10k documentos

Parâmetros Detalhados

operation (string, obrigatório)

O que é: Tipo de operação a ser executada no Elasticsearch.

Valores válidos: - index_document: Indexar documento - search: Buscar documentos - get_document: Obter documento por ID - update_document: Atualizar documento - delete_document: Deletar documento - create_index: Criar índice - delete_index: Deletar índice

Flow completo para testar:

{
  "name": "Teste ES - Index Document",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "es_1",
      "type": "elasticsearch",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Indexar Produto",
        "parameters": {
          "operation": "index_document",
          "index": "products",
          "document": {
            "name": "Notebook Dell",
            "price": 3500,
            "category": "electronics"
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Confirmar",
        "parameters": {
          "message": "Produto indexado! ID: {{es_1.documentId}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "es_1" },
    { "source": "es_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Documento é indexado no Elasticsearch.

index (string, obrigatório)

O que é: Nome do índice Elasticsearch (equivalente a tabela em SQL).

Flow completo para testar:

{
  "name": "Teste ES - Index Name",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "es_1",
      "type": "elasticsearch",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Criar Índice",
        "parameters": {
          "operation": "create_index",
          "index": "users",
          "mapping": {
            "properties": {
              "name": { "type": "text" },
              "email": { "type": "keyword" },
              "age": { "type": "integer" }
            }
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Confirmar",
        "parameters": {
          "message": "Índice 'users' criado!"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "es_1" },
    { "source": "es_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Índice é criado com mapping especificado.

document (object, obrigatório para index/update)

O que é: Objeto com dados do documento a ser indexado/atualizado.

Flow completo para testar:

{
  "name": "Teste ES - Document",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Pedir Nome",
        "parameters": {
          "message": "Nome do produto:",
          "variable": "produto"
        }
      }
    },
    {
      "id": "number_1",
      "type": "number",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Pedir Preço",
        "parameters": {
          "message": "Preço:",
          "variable": "preco",
          "decimals": 2
        }
      }
    },
    {
      "id": "es_1",
      "type": "elasticsearch",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Indexar",
        "parameters": {
          "operation": "index_document",
          "index": "catalog",
          "document": {
            "name": "{{produto}}",
            "price": "{{preco}}",
            "timestamp": "{{now}}",
            "status": "active"
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Confirmar",
        "parameters": {
          "message": "{{produto}} indexado! Preço: R$ {{preco}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "input_1" },
    { "source": "input_1", "target": "number_1" },
    { "source": "number_1", "target": "es_1" },
    { "source": "es_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Documento com variáveis é indexado.

O que é: Query DSL do Elasticsearch para buscar documentos.

Flow completo para testar:

{
  "name": "Teste ES - Search Query",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Termo de Busca",
        "parameters": {
          "message": "O que você procura?",
          "variable": "termo"
        }
      }
    },
    {
      "id": "es_1",
      "type": "elasticsearch",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Buscar",
        "parameters": {
          "operation": "search",
          "index": "products",
          "query": {
            "query": {
              "match": {
                "name": "{{termo}}"
              }
            }
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Resultado",
        "parameters": {
          "message": "Encontrados: {{es_1.totalHits}} resultados"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "input_1" },
    { "source": "input_1", "target": "es_1" },
    { "source": "es_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Busca por termo retorna resultados.

url (string, opcional)

O que é: URL do cluster Elasticsearch (padrão: http://localhost:9200).

apiKey (string, opcional)

O que é: API Key para autenticação no Elasticsearch.

id (string, obrigatório para get/update/delete)

O que é: ID do documento para operações específicas.

mapping (object, opcional)

O que é: Mapping do índice ao criar (define tipos de campos).

Parâmetros

Campo Tipo Obrigatório Descrição
operation string Sim index_document, search, get_document, update_document, delete_document, create_index, delete_index
index string Sim Nome do índice
document object Sim* Documento a indexar/atualizar (*para index/update)
query object Sim* Query DSL (*para search)
id string Sim* ID do documento (*para get/update/delete)
url string Não URL do ES (padrão: http://localhost:9200)
apiKey string Não API Key para autenticação
mapping object Não Mapping ao criar índice

Exemplo 1: Busca de Produtos

Objetivo: Indexar e buscar produtos em catálogo

JSON para Importar

{
  "name": "ES - Busca de Produtos",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "es_1",
      "type": "elasticsearch",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Indexar Notebooks",
        "parameters": {
          "operation": "index_document",
          "index": "products",
          "document": {
            "name": "Notebook Dell Inspiron",
            "price": 3200,
            "category": "electronics",
            "brand": "Dell",
            "inStock": true
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "delay_1",
      "type": "delay",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Aguardar Indexação",
        "parameters": {
          "duration": 1,
          "unit": "seconds"
        }
      }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Buscar",
        "parameters": {
          "message": "O que você procura?",
          "variable": "busca"
        }
      }
    },
    {
      "id": "es_2",
      "type": "elasticsearch",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Buscar Produtos",
        "parameters": {
          "operation": "search",
          "index": "products",
          "query": {
            "query": {
              "multi_match": {
                "query": "{{busca}}",
                "fields": ["name", "category", "brand"]
              }
            }
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Resultados",
        "parameters": {
          "message": "Encontrados {{es_2.totalHits}} produtos para '{{busca}}'"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1300, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "es_1" },
    { "source": "es_1", "target": "delay_1" },
    { "source": "delay_1", "target": "input_1" },
    { "source": "input_1", "target": "es_2" },
    { "source": "es_2", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: O que você procura?
Usuário: notebook
Sistema: Encontrados 1 produtos para 'notebook'

Exemplo 2: Log Aggregation

Objetivo: Indexar logs e buscar erros

JSON para Importar

{
  "name": "ES - Log Aggregation",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "es_1",
      "type": "elasticsearch",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Indexar Log",
        "parameters": {
          "operation": "index_document",
          "index": "application-logs",
          "document": {
            "level": "error",
            "service": "api-gateway",
            "message": "Connection timeout to database",
            "timestamp": "{{now}}",
            "userId": 456
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Log Indexado",
        "parameters": {
          "message": "Log de erro indexado!"
        }
      }
    },
    {
      "id": "es_2",
      "type": "elasticsearch",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Buscar Erros",
        "parameters": {
          "operation": "search",
          "index": "application-logs",
          "query": {
            "query": {
              "bool": {
                "must": [
                  { "match": { "level": "error" } },
                  { "match": { "service": "api-gateway" } }
                ]
              }
            },
            "size": 10
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Total Erros",
        "parameters": {
          "message": "Total de erros no API Gateway: {{es_2.totalHits}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "es_1" },
    { "source": "es_1", "target": "message_1" },
    { "source": "message_1", "target": "es_2" },
    { "source": "es_2", "target": "message_2" },
    { "source": "message_2", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: Log de erro indexado!
Sistema: Total de erros no API Gateway: 1

Exemplo 3: Atualizar Documento

Objetivo: Atualizar preço de produto

JSON para Importar

{
  "name": "ES - Atualizar Produto",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "ID do Produto",
        "parameters": {
          "message": "ID do produto:",
          "variable": "productId"
        }
      }
    },
    {
      "id": "number_1",
      "type": "number",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Novo Preço",
        "parameters": {
          "message": "Novo preço:",
          "variable": "novoPreco",
          "decimals": 2
        }
      }
    },
    {
      "id": "es_1",
      "type": "elasticsearch",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Atualizar",
        "parameters": {
          "operation": "update_document",
          "index": "products",
          "id": "{{productId}}",
          "document": {
            "price": "{{novoPreco}}",
            "updatedAt": "{{now}}"
          },
          "url": "http://localhost:9200"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Confirmar",
        "parameters": {
          "message": "Preço atualizado para R$ {{novoPreco}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "input_1" },
    { "source": "input_1", "target": "number_1" },
    { "source": "number_1", "target": "es_1" },
    { "source": "es_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: ID do produto:
Usuário: abc123
Sistema: Novo preço:
Usuário: 2999.90
Sistema: Preço atualizado para R$ 2999.90

Resposta do Node

Index Document

{
  "success": true,
  "action": "elasticsearch_document_indexed",
  "index": "products",
  "documentId": "abc123xyz",
  "result": "created",
  "timestamp": "2025-01-15T10:30:00.000Z"
}
{
  "success": true,
  "action": "elasticsearch_search_completed",
  "index": "products",
  "totalHits": 42,
  "documents": [ /* array de documentos */ ],
  "took": 5,
  "timestamp": "2025-01-15T10:30:00.000Z"
}

Query DSL Exemplos

Match simples

{
  "query": {
    "match": {
      "name": "notebook"
    }
  }
}

Bool com filtros

{
  "query": {
    "bool": {
      "must": [
        { "match": { "category": "electronics" } }
      ],
      "filter": [
        { "range": { "price": { "gte": 1000, "lte": 5000 } } }
      ]
    }
  }
}

Multi-match

{
  "query": {
    "multi_match": {
      "query": "dell laptop",
      "fields": ["name^2", "description", "brand"]
    }
  }
}

Boas Práticas

SIM:

  • Defina mappings antes de indexar para controle de tipos
  • Use query bool para combinar múltiplos critérios
  • Implemente paginação (from/size ou search_after)
  • Use analyzers apropriados para texto em português
  • Monitore uso de memória e disk space

NÃO:

  • Não indexe dados sensíveis sem encryption
  • Não faça queries sem limites (sempre defina size)
  • Não use wildcard queries no início (ex: *termo)
  • Não ignore índice patterns e lifecycle policies
  • Não mantenha índices antigos desnecessariamente

Dicas

💡 Dev Tools: Use Kibana Dev Tools para testar queries

💡 Análise: Configure analyzers para melhor busca (stemming, stopwords)

💡 Performance: Use filtros (filter) ao invés de queries quando possível

💡 Relevância: Ajuste scoring com boost e function_score

Próximo Node

MONGODB - NoSQL database → POSTGRES - SQL database → REDIS - Cache e busca rápida