Jornada Inicial com Neo4j

Thiago Maranhão
cajudevs
Published in
11 min readDec 20, 2023

Neste artigo vou mostrar um pouco da minha jornada inicial com o Neo4j, o banco de dados de grafos.

Aqui você encontrará os seguintes tópicos:

  • Referências de Cursos
  • Conceitos
  • Processo de Modelagem de dados em grafos
  • Laboratório Neo4j na prática

Observação: O intuito deste artigo é materializar o conhecimento adquirido durante os cursos realizados, e no momento que o mesmo está sendo escrito, sou um iniciante na tecnologia, então, posso cometer algumas falhas ou não estar utilizando as melhores práticas. Se você encontrar algum erro ou sugestão de melhoria, ficarei muito feliz em receber o seu feedback para que eu possa melhorar ou corrigir o texto.

Caso você nunca tenha ouvido falar em Neo4j, Grafos ou Banco de dados em Grafos, vou deixar aqui um resumo bem sucinto sobre o que se trata.

Grafos são estruturas matemáticas que consistem em Arestas e vértices.

Bancos de dados em grafos, como o Neo4j usam o Grafo de Propriedade, onde os elementos do grafo possuem propriedades. Nesse tipo de grafo, as Arestas são conhecidas como Relacionamentos e os Vértices são conhecidos como Nós.

Neo4j é um banco de dados em grafos nativo desenvolvido especialmente para percorrer o grafo.

Cursos Neo4J

Primeiramente, é importante deixar aqui o link de acesso ao site do Neo4j, que é muito rico e traz muitas informações, desde cursos e certificações gratuitas, até a criação de uma instância gratuita do AuraDB para pequenos projetos ou estudos.

Para escrever esse artigo fiz os 3 cursos gratuitos abaixo disponíveis no próprio site do Neo4j, em uma plataforma chamada Graph Academy.

São 3 cursos de curta duração, mas que trazem informações muito importantes com exemplos.

É importante frisar que os cursos são na língua inglesa.

Colocando o conhecimento em prática

Ultimamente eu tenho valorizado muito o conhecimento aplicado. Aquele conhecimento que adquirimos para resolver algum problema específico do momento.

Porém, o que fazer quando estamos querendo aprender uma nova tecnologia, ou agregar um conhecimento, que em princípio não resolve um problema corrente do nosso dia a dia?

Simples, basta criarmos o problema.

Foi com essa mentalidade que resolvi me desafiar a criar um modelo de dados em Grafos usando como problema/domínio algo que faz parte da minha rotina. Sou praticante de corrida de rua há 9 anos e usei essa experiência para gerar uma necessidade onde eu pudesse aplicar o novo conhecimento adquirido.

A proposta será modelar um sistema que controle as corridas realizadas por um corredor.

Para tanto, iremos utilizar o processo de modelagem de dados em grafos, conforme os passos abaixo:

  1. Entendimento do domínio e definição dos casos de uso específicos (questões) que a aplicação deve resolver (responder).
  2. Desenvolvimento do modelo de dados de grafos inicial: Modelagem dos nós (entidades) e Modelagem dos relacionamentos entre os nós.
  3. Teste do caso de uso contra o modelo de dados inicial.
  4. Criar o grafo (modelo de instância) testando os dados usando Cypher.
  5. Teste dos casos de uso, incluindo testes de performance.
  6. Refatorar (melhorar) o modelo de dados do grafo para atender a uma mudança em um caso de uso chave ou por razões de performance.
  7. Implementar a refatoração no grafo (modelo de instância) e retestar usando Cypher.

Passo 1: Entendimento do domínio e definição dos casos de uso

Desejamos criar um sistema que seja capaz de gerenciar/controlar os treinos de um corredor e as competições que o corredor participou.

Para isso, entrevistamos alguns stakeholders, os praticantes do esporte, e levantamos os seguintes casos de uso (questões) que devem ser resolvidos pelo sistema:

  • Qual é o número total de competições que o corredor participou em um determinado período?
  • Qual é o número total de treinos que o corredor realizou em um determinado período?
  • Qual foi a maior distância percorrida pelo corredor em um determinado período?
  • Qual foi o melhor ritmo em uma corrida realizada pelo corredor em um determinado período?
  • Qual foi o maior tempo de corrida realizado pelo corredor em um determinado período?

Passo 2: Desenvolvimento do modelo de grafos inicial

Nessa etapa, iremos listar cada um dos casos de uso e iremos descrever os passos que podemos executar para responder as questões:

CASO DE USO 1: Qual é o número total de competições que o corredor participou em um determinado período?

  • Obtenha o corredor pelo seu nome.
  • Obtenha as competições que o corredor participou no período informado.
  • Registre a quantidade de competições.

CASO DE USO 2: Qual é o número total de treinos que o corredor realizou em um determinado período?

  • Obtenha o corredor pelo seu nome.
  • Obtenha os treinos que o corredor participou no período informado.
  • Registre a quantidade de treinos.

CASO DE USO 3: Qual foi a maior distância percorrida pelo corredor em um determinado período?

  • Obtenha o corredor pelo seu nome.
  • Obtenha os treinos e competições que o corredor participou no período informado.
  • Obtenha a maior distância.

CASO DE USO 4: Qual foi o melhor ritmo realizado pelo corredor em um determinado período?

  • Obtenha o corredor pelo seu nome.
  • Obtenha os treinos e competições que o corredor participou no período informado.
  • Obtenha o melhor ritmo.

CASO DE USO 5: Qual foi o maior tempo de corrida realizado pelo corredor em um determinado período?

  • Obtenha o corredor pelo seu nome.
  • Obtenha os treinos e competições que o corredor participou no período informado.
  • Obtenha o maior tempo de corrida.

Baseado nos casos de uso e nos passos descritos acima podemos chegar nos seguintes detalhes:

O corredor é uma entidade que possui as seguintes propriedades:

  • nome;

Treino e competição são tipos de corrida.

A corrida é uma entidade que precisa ter as seguintes propriedades:

  • nome (para identificar a corrida);
  • tipo (para diferenciar o treino da competição);
  • data (para podermos filtrar por período);
  • distância (distância percorrida na corrida);
  • tempo (tempo gasto na corrida);
  • ritmo (ritmo realizado durante a corrida);

O corredor realiza uma corrida, então inicialmente só precisamos de um relacionamento entre a entidade corredor e a entidade corrida, que podemos representar por [:REALIZOU].

  • (:Corredor) — [:REALIZOU] → (:Corrida);

Observação: Notações para representação de grafos

  • (: Nó ou Entidade) : um nó ou entidade é representada entre parênteses
  • [:RELACIONAMENTO] : um relacionamento é representado entre colchetes
  • A representação de um grafo com relacionamento no Neo4j precisa ser direcional, ou seja, inicia em um nó, passa por um relacionamento e finaliza em um nó. Um traço “-” indica o início do relacionamento e o traço com uma seta “→” indica o final do relacionamento
  • Apesar de não ser uma boa pratica, podemos colocar apenas traços e ele sempre considerará a direção da esquerda para a direita.

Se pensarmos nos dados que serão utilizados, também já conseguimos descrever os tipos de dados que precisaremos usar:

Corredor

  • nome: string

Corrida

  • nome: string
  • data: date (dd-mm-yyyy)
  • tipo: string [Treino, Competição]
  • distancia: decimal
  • tempo: time (hh:mm:ss)
  • ritmo: time (min:sec)

Diante desses detalhes já podemos desenhar a primeira versão dos nossos modelos: o modelo de dados e o modelo de instância.

Observação: Para o desenho inicial dos modelos estamos utilizando a ferramenta arrows.app

Modelo de Dados

Modelo de Instância

Passo 3: Testando os casos de uso contra o modelo de dados criados

Tendo definido o modelo de dados inicial, podemos fazer o teste dos casos de uso. Para isso vamos usar o Cypher para construir as queries que serão utilizadas para validação.

Observações:

  • Cypher é a linguagem de consulta utilizada pelo Neo4j. Para referência, acesse a página do Cypher Cheat Sheet
  • É importante que este teste seja realizado já dentro do ambiente do banco de dados em Grafos para que você possa de fato executar suas queries em Cypher e testar os seus modelos. Para isso, basta utilizar o AuraDB Free disponibilizado pela própria Neo4J

Iremos criar o grafo com os nós e relacionamentos que exemplificamos até o momento dentro do AuraDB para que possamos fazer as validações.

Scripts de Criação dos nós e relacionamentos:

// Criar Nós - (:Corredor)
MERGE
(:Corredor {nome:"Thiago Maranhão"})
MERGE
(:Corredor {nome:"Allan Victor"})

// Criar Nós - (:Corrida)
MERGE
(corrida:Corrida {nome:"Meia da Conceição"})
SET
corrida.data="2023-12-10",
corrida.tipo="competicao",
corrida.distancia=21.11,
corrida.tempo="01:50:16",
corrida.ritmo="00:05:13"

MERGE
(corrida:Corrida {nome:"5Km Regenerativo"})
SET
corrida.data="2023-12-12",
corrida.tipo="treino",
corrida.distancia=5,
corrida.tempo="00:30:07",
corrida.ritmo="00:06:01"

MERGE
(corrida:Corrida {nome:"Corrida Matinal"})
SET
corrida.data="2023-12-13",
corrida.tipo="treino",
corrida.distancia=10.81,
corrida.tempo="00:52:49",
corrida.ritmo="00:04:53"

// Criar Relacionamentos [:REALIZOU]
MATCH
(thiago:Corredor {nome:"Thiago Maranhão"}),
(conceicao:Corrida {nome:"Meia da Conceição"})
MERGE
(thiago)-[realizou:REALIZOU]->(conceicao)

MATCH
(thiago:Corredor {nome:"Thiago Maranhão"}),
(cincokm:Corrida {nome:"5Km Regenerativo"})
MERGE
(thiago)-[:REALIZOU]->(cincokm)

MATCH
(allan:Corredor {nome:"Allan Victor"}),
(corr_matinal:Corrida {nome:"Corrida Matinal"})
MERGE
(allan)-[:REALIZOU]->(corr_matinal)c

Visualização dos nós e relacionamentos no AuraDB após a execução dos scripts Cypher:

Agora que temos uma massa de dados inicial no nosso banco de dados, precisamos validar os nossos casos de uso através de testes usando o Cypher e a própria base para verificarmos se todas as perguntas podem ser respondidas:

CASO DE USO 1: Qual é o número total de competições que o corredor participou em um determinado período?

Como atender o caso de uso:

  • Obtenha o corredor pelo seu nome
  • Obtenha as competições que o corredor participou no período informado
  • Conte a quantidade de competições

Query Cypher:

MATCH (corredor:Corredor) - [:REALIZOU] -> (corrida:Corrida)
WHERE
corrida.tipo = "competicao" and
corrida.data >= "2023-12-01" and
corrida.data <= "2023-12-31" and
corredor.nome = "Thiago Maranhão"
RETURN corredor.nome, count(*)

Resultado:

CASO DE USO 2: Qual é o número total de treinos que o corredor realizou em um determinado período?

Como atender o caso de uso:

  • Obtenha o corredor pelo seu nome
  • Obtenha os treinos que o corredor participou no período informado
  • Conte a quantidade de treinos

Query Cypher:

MATCH (corredor:Corredor) - [:REALIZOU] -> (corrida:Corrida)
WHERE
corrida.tipo = "treino" and
corrida.data >= "2023-12-01" and
corrida.data <= "2023-12-31" and
corredor.nome = "Allan Victor"
RETURN corredor.nome, count(*)

Resultado:

CASO DE USO 3: Qual foi a maior distância percorrida pelo corredor em um determinado período?

Como atender o caso de uso:

  • Obtenha o corredor pelo seu nome
  • Obtenha os treinos e competições que o corredor participou no período informado
  • Obtenha a maior distância

Query Cypher:

MATCH (corredor:Corredor) - [:REALIZOU] -> (corrida:Corrida)
WHERE
corrida.data >= "2023-12-01" and
corrida.data <= "2023-12-31" and
corredor.nome = "Thiago Maranhão"
RETURN corredor.nome, MAX(corrida.distancia)

Resultado:

CASO DE USO 4: Qual foi o melhor ritmo realizado pelo corredor em um determinado período?

Como atender o caso de uso:

  • Obtenha o corredor pelo seu nome
  • Obtenha os treinos e competições que o corredor participou no período informado
  • Obtenha o melhor ritmo

Query Cypher:

MATCH (corredor:Corredor) - [:REALIZOU] -> (corrida:Corrida)
WHERE
corrida.data >= "2023-12-01" and
corrida.data <= "2023-12-31" and
corredor.nome = "Thiago Maranhão"
RETURN corredor.nome, MIN(corrida.ritmo)

Resultado:

CASO DE USO 5: Qual foi o maior tempo de corrida realizado pelo corredor em um determinado período?

Como atender o caso de uso:

  • Obtenha o corredor pelo seu nome
  • Obtenha os treinos e competições que o corredor participou no período informado
  • Obtenha o maior tempo de corrida

Query Cypher:

MATCH (corredor:Corredor) - [:REALIZOU] -> (corrida:Corrida) 
WHERE
corrida.data >= "2023-12-01" and
corrida.data <= "2023-12-31" and
corredor.nome = "Thiago Maranhão"
RETURN corredor.nome, MAX(corrida.tempo)

Resultado:

PASSO 4: Refatoração do modelo de dados

Nesta etapa, a ideia é refatorar (melhorar) o modelo de dados do grafo para atender a uma mudança em um caso de uso chave ou por razões de performance, e em seguida implementar a refatoração no grafo (modelo de instância) e retestar usando Cypher.

Para o nosso cenário iremos considerar um cenário que nos levará a uma necessidade de refatoração principalmente a medida que a massa de dados for aumentando. Já notamos que os casos de uso nos mostram a necessidade de diferenciar as corridas por tipo: Competição ou Treino.

Da forma como o nosso modelo está definido atualmente, precisamos percorrer todas as corridas realizadas por um corredor para descobrir o tipo de corrida que foi realizada.

Já pensando em performance, podemos propor uma pequena mudança que fará com que esse tipo de consulta seja otimizada.

A mudança simples é trazer o campo tipoCorrida para o relacionamento :REALIZOU.

Dessa forma os nossos modelos atualizados ficariam da seguinte forma:

Modelo de Dados (Refatorado)

Modelo de Instância (Refatorado)

Agora precisamos aplicar a refatoração no nosso banco de dados e em seguida realizar testes usando o Cypher e verificar se continuaremos tendo os mesmos resultados:

Código Cypher para Refatoração

MATCH (corredor:Corredor) - [realizou:REALIZOU] -> (corrida:Corrida)
SET realizou.tipoCorrida = corrida.tipo

Agora já podemos validar se ainda conseguimos responder às mesmas perguntas usando a última mudança.

Somente dois casos de uso serão afetados, pois são os casos de uso que dependem do tipo de corrida:

CASO DE USO 1: Qual é o número total de competições que o corredor participou em um determinado período?

Query Cypher:

  • Vamos trocar no filtro o campo tipo do nó Corrida pelo tipoCorrida do relacionamento :REALIZOU.
MATCH (corredor:Corredor) - [realizou:REALIZOU] -> (corrida:Corrida)
WHERE
realizou.tipoCorrida = "competicao" and
corrida.data >= "2023-12-01" and
corrida.data <= "2023-12-31" and
corredor.nome = "Thiago Maranhão"
RETURN corredor.nome, count(*)

Resultado:

CASO DE USO 2: Qual é o número total de treinos que o corredor realizou em um determinado período?

Query Cypher:

  • Vamos trocar no filtro o campo tipo do nó Corrida pelo tipoCorrida do relacionamento :REALIZOU.
MATCH (corredor:Corredor) - [realizou:REALIZOU] -> (corrida:Corrida)
WHERE
realizou.tipoCorrida = "treino" and
corrida.data >= "2023-12-01" and
corrida.data <= "2023-12-31" and
corredor.nome = "Allan Victor"
RETURN corredor.nome, count(*)

Resultado:

Validamos que o nosso modelo de dados está consistente após a refatoração.

Diante disso, podemos remover o campo “tipo” do nó Corrida, uma vez que não precisaremos mais dele.

MATCH (corrida: Corrida)
SET corrida.tipo = NULL

Poderíamos também obter o mesmo resultado usando o comando REMOVE:

MATCH (corrida:Corrida)
REMOVE corrida.tipo

Conclusão

Foi uma experiência legal conhecer o mundo dos grafos e dos bancos de dados em grafos e sua aplicação.

O site da neo4j e a plataforma GraphAcademy demonstraram ser bastante intuitivas e de grande valor, trazendo todos os recursos necessários para o aprendizado e a prática, a custo zero.

Para finalizar, gostaria de deixar aqui alguns cenários/casos de uso conhecidos por serem implementados em grafos para que seja possível materializar melhor a aplicação dessa opção adicional de banco de dados:

  • Recomendações em tempo real em plataformas de e-commerce
  • Análise de riscos e fraudes
  • Melhoria operacional em transportes e logística

Repositório contendo os scripts Cypher para a criação do Banco de Dados e com as Queries de consulta

Agradecimento especial a Allan Victor de Menezes Santos , Marcelo Oliveira Calumbi, Mateus G. Geracino, Fernando Mota da Silva pela revisão do texto e sugestões de melhoria.

Ps: Esse é o primeiro artigo que escrevo, se você leu, gostou e acha que esse formato de texto pode ajudar ou ser de interesse de outras pessoas, peço que compartilhe e me comenta a sua opinião para que eu possa ir gradativamente melhorando nos próximos artigos ou até ajustar esse artigo conforme os feedbacks recebidos. Obrigado!!!

--

--