A modelagem e análise de relacionamentos é um dos cases mais aplicados no mercado. A escolha de qual produto recomendar ao cliente, definir qual filme ou série sugerir ou modelar a sua rede de contato com a proximidade das pessoas em relação ao seu perfil são exemplos que podemos encontrar nos principais serviços da internet.
Sobre este último, será a abordagem utilizada para este artigo. Para isso, será demonstrada a modelagem utilizando grafos e Neo4J (sistema de gerenciamento de banco de dados gráficos), a API em Python que irá gerar as entidades de formas aleatórias e a utilização de Docker para padronizar os ambientes.
👩🏫 O problema
Atualmente, uma rede de contatos de um profissional é muito relevante, principalmente em eventos corporativos. A importância de ter conexões no mesmo ramo de conhecimento traz benefícios, tais como: comunicação com pessoas que entendem do assunto e possíveis dúvidas podem ser esclarecidas, indicações de trabalhos disponíveis, entre outros.
Uma empresa de tecnologia decide realizar um evento com objetivo de apresentar e vender o seu novo portfólio, com diversas palestras, mesas redondas e stands com as maiores referências de especialistas no mercado. Além disso, o evento também busca conectar pessoas dessa área, visto que existem ramificações bem específicas tais como: exatas, biológicas e humanas.
Cada pessoa preenche seu nome, data de nascimento, email, cidade, estado e cargo que ocupa no momento. As empresas são registradas com nome, cidade, estado e área de atuação. As universidades possuem o registro do nome, estado e cidade.
😰 Mas o que são grafos?
Para entender melhor a teoria de grafos ou até mesmo relembrar seus principais conceitos, visite a breve introdução que preparamos no artigo Grafos, teoria e aplicações.
## Grafos, teoria e aplicações
🌍 Modelando com Neo4J
O banco de dados de grafos é um dos tipos de bancos de dados NoSQL. Ele é diretamente relacionado a um modelo (grafos) de dados estabelecido, eles foram criado para possibilitar o armazenamento de relacionamentos e navegação por eles. As entidades são armazenadas como nós e os relacionamentos entre elas são as arestas, nas quais possuem direcionamento.
Dentre as opções de bancos de dados gráficos, destaca-se o Neo4J. Ele é um banco confiável, escalável e de alto desempenho, suas características adequadas de ACID são a base da confiabilidade dos dados. O banco garante que as operações que envolvem a modificação de dados ocorram dentro de uma transação para garantir dados consistentes.
ACID (Atomicidade, Consistência, Isolamento e Durabilidade) é um conjunto de propriedades que garante que as transações do banco de dados sejam processadas com confiabilidade, sendo a transação uma operação lógica única no banco de dados.
👨💻 Show Me the Code!
Antes de utilizar a API para desenhar toda a complexidade do sistema, é importante passar pelos comandos básicos do banco, como criar os nós e os relacionamentos. Esses comandos podem ser executados diretamente na interface gráfica da ferramenta. Para este artigo, foi utilizada a imagem Docker oficial do Neo4J, disponível no Docker Hub.
Interface do Neo4J acessada pelo endereço localhost:7474
Para interagir com os comandos, iremos modelas a mesma aplicação, porém com uma quantidade menor de objetos, depois apagamos tudo e deixamos que a API faça todo o ‘trabalho grosso’.
- Criando pessoas
CREATE (p1:Pessoa {Nome: 'Cintia'})
CREATE (p2:Pessoa {Nome: 'Lennon'})
CREATE (p3:Pessoa {Nome: 'Mateus'})
- Criando universidade
CREATE (u1:Universidade {Nome: 'FIAP'})
- Criando empresas
CREATE (e1:Empresa {Nome: 'FICO'})
CREATE (e2:Empresa {Nome: 'XP Inc'})
CREATE (e3:Empresa {Nome: 'Lumini'})
Após criar os nós, podemos visualizar graficamente as operações.
Para isso executamos
MATCH(m1) WHERE id(m1) >= 0 RETURN m1
Visualização gráfica de pessoa(s), universidade(s) e empresa(s)
- Criando relacionamentos entre pessoas e universidade
MATCH(p1),(u1) WHERE p1.Nome='Cintia' AND u1.Nome='FIAP'
CREATE (p1)-\[r:ESTUDA\]->(u1)
MATCH(p2),(u1) WHERE p2.Nome='Lennon' AND u1.Nome='FIAP'
CREATE (p2)-\[r:ESTUDA\]->(u1)
MATCH(p3),(u1) WHERE p3.Nome='Mateus' AND u1.Nome='FIAP'
CREATE (p3)-\[r:ESTUDA\]->(u1)
- Criando relacionamentos entre pessoas e empresas
MATCH(p1),(e1) WHERE p1.Nome='Cintia' AND e1.Nome='FICO'
CREATE (p1)-\[r:TRABALHA\]->(e1)
MATCH(p2),(e2) WHERE p2.Nome='Lennon' AND e2.Nome='XP Inc'
CREATE (p2)-\[r:TRABALHA\]->(e2)
MATCH(p3),(e3) WHERE p3.Nome='Mateus' AND e3.Nome='Lumini'
CREATE (p3)-\[r:TRABALHA\]->(e3)
É possível visualizar de maneira gráfica as ligações entre os nós, formadas pelos relacionamentos cadastrados.
Visualização gráfica dos objetos com os relacionamentos
- Criando/alterando atributos
MATCH(p1) WHERE p1.Nome='Lennon'
SET p1.Cidade = 'São Paulo'MATCH(u1) WHERE u1.Nome='FIAP'
SET u1.Cidade = 'São Paulo'
Com poucos nós cadastrados, já é possível realizar algumas consultas, como: “Quais pessoas trabalham na empresa XP Inc?”
Visualização gráfica da consulta realizada pelo banco
Hora de usar a API para gerar os dados, mas antes, utilizaremos alguns comandos para deletar a estrutura criada.
Você pode encontrar uma introdução mais completa acessando a página de treinamento online do Neo4J.
- Deletando relacionamento entre nós
MATCH (p1 {Nome: 'Lennon'})-\[r:ESTUDA\]->(u1 {Nome: 'FIAP'}) DELETE rMATCH (p1 {Nome: 'Lennon'})-\[r:TRABALHA\]->(u1 {Nome: 'XP Inc'}) DELETE r
- Deletando nós
MATCH (p1 {Nome: 'Lennon'}) DELETE p1MATCH (e1 {Nome: 'XP Inc'}) DELETE e1
Os comandos acima foram para exemplificar como deletar nós e/ou relacionamentos, sendo possível agrupá-los e deletar ambos de uma só vez. Para economizar tempo, podemos excluir todos os cadastros do banco com o seguinte comando:
MATCH (n) DETACH DELETE n
O restante do artigo utilizará a aplicação desenvolvida para gerar todos os registros. Entenderemos então como parametrizar a API e como padronizamos os ambientes para que você possa executar em qualquer máquina que contenha Docker e Docker Compose.
🚀 Escalando com Python API
Para simular os objetos e criar um ambiente mais próximo ao que costumamos encontrar em eventos, foi construida uma API utilizando a linguagem Python. A função dessa API é receber parâmetros quantitativos e gerar aleatoriamente n pessoas, n empresas e n universidades. Com esses objetos gerados, a API utiliza uma distribuição randômica para gerar os relacionamentos entre eles.
Com essa aplicação, é possível escalar a complexidade do modelo de acordo com os parâmetros informados no arquivo app.py
.
🐋 Padronizando com Docker
Foi utilizado o Docker (plataforma de código aberto para criação e administração de ambientes isolados) para manter a padronização do ambiente, das instalações e dos versionamentos das ferramentas e tecnologias utilizadas no projeto.
Para que os serviços mapeados no docker-compose.yml
sejam startados, basta executar o comando após o clone do repositório da aplicação.
$ docker-compose up --build
Para a execução do comando é necessário que o Docker e o Docker Compose estejam instalados e corretamente configurados em sua máquina.
💪 O poder das nossas buscas
Após a execução da API, temos o seguinte resultado ao consultar toda a nossa base de relacionamentos:
Visualização gráfica dos objetos com os relacionamentos
É notável que a complexidade aumentou um pouco e que a API se tornou bem útil ao não precisarmos realizar todas as operações de inserção na mão. Ao total temos 134 nós, sendo 60 pessoas, 44 empresas e 30 universidades e 120 relacionamentos, sendo 60 do tipo ‘ESTUDA’ e 60 do tipo ‘TRABALHA’.
Chegou a hora de ver o que podemos fazer: “Quantos alunos cada universidade tem, em ordem decrescente e que sejam Top 5?”
MATCH (p:Pessoa)-\[:ESTUDA\]->(u:Universidade)
RETURN u.Nome, count(\*) AS alunos
ORDER BY pessoas DESC
LIMIT 5
Resultado da consulta de universidades com mais alunos
E que tal “Quais pessoas estudam ou trabalham com Thiago Hugo Paulo da Mota?”
MATCH (p1:Pessoa)-\[:TRABALHA\]->(e:Empresa),
(p1)-\[:ESTUDA\]->(u:Universidade),
(p2:Pessoa)-\[:TRABALHA\]->(e),
(p3:Pessoa)-\[:ESTUDA\]->(u)
WHERE p1.Nome = 'Thiago Hugo Paulo da Mota'
RETURN \*
Resultado da consulta de pessoas próximas ao Thiago
A utilização de um banco de dados gráfico resolve problemas mais complexos de relacionamento com facilidade e alta performance.
Por fim, o exemplo clássico da utilização de grafos, encontrar o caminho mais curto. “Os alunos da Universidade Mackenzie gostariam de compartilhar conhecimento com os alunos da Universidade do Amazonas, qual é o caminho mais curto para que esse encontro seja possível?”
MATCH p = shortestPath((u1:Universidade)-\[\*\]-(u2:Universidade))
WHERE u1.Nome = 'Universidade Mackenzie' AND
u2.Nome = 'Universidade do Amazonas'
RETURN p
Resultado da consulta de menor caminho entre um aluno da Universidade Mackenzie e um aluno da Universidade do Amazonas
É possível explorar o poder da performance de um banco de dados de grafos, alterando e incrementando os exemplos citados. Nos links disponíveis existem também treinamentos para extrair o máximo poder da ferramenta.
O projeto completo você pode encontrar no repositório do GitHub.