Back-End

29 mar, 2012

Diversão com Neo4J e JRuby

Publicidade

Neo4J é um banco de dados baseado em grafos feito em Java com suporte completo para Transactions. Ele trabalha com nós e relacionamentos, você é capaz de definir propriedades a ambos, a estrutura do gráfico é bem simples, mas tem grande expressividade e bom trabalho para alguns domínios de problemas.

Grafos são perfeitos para as mídias sociais, porque você tem amigos, e os amigos têm amigos e eles têm twitter, e-mail, celulares. Você tem propriedades nos relacionamentos, como por quanto tempo você conhece esse amigo ou quando você o viu pela última vez etc.

Esse é o exemplo do relacionamento do clássico filme Matrix. Vamos ver como estruturar esse grafo no código, vou mostrar o código Ruby através do JRuby. Fiz algumas alterações e adicionei novas propriedades, mas o relacionamento não mudou tanto.

#
# I just create nodes with sumbol and put whatever you want
#
neo = Neo4j::Node.new(:name => 'Neo', :realname => "Thomas Anderson", :rank => "The One")
morpheus = Neo4j::Node.new(:name => 'Morpheus', :rank => "Captain", :occupation => "total badass")
trinity = Neo4j::Node.new(:name => 'Trinity', :rank => "Girlfriend")
smith = Neo4j::Node.new(:name => 'Smith', :language => "C++", :version => "1.0b", :rank => "-1")
architect = Neo4j::Node.new(:name => 'Architect', :rank => "0")
cypher = Neo4j::Node.new(:name => 'Cypher', :rank => "sucks")

#
# I'm defining a couple of relationships
#
neo.outgoing(:knows) << trinity
neo.outgoing(:knows) << morpheus
morpheus.outgoing(:knows) << trinity
morpheus.outgoing(:knows) << cypher
cypher.outgoing(:knows) << smith
smith.outgoing(:knows) << architect

#
# Here we have relationship properties(and my monkey ptach)
#
neo.outgoing(:knows).first[:since] = "3 days"
neo.outgoing(:knows).second[:since] = "1 day"

Eu usei Neo4j::Node para criar os nós. Ao criar um nó, você pode definir propriedades, utilizando, para isso, símbolos em Ruby (como criando um mapa), e você pode definir tantas propriedades quantas desejar. Como você pode ver, eu defini algumas propriedades comuns para cada nó, mas também há propriedades únicas para alguns nós, como :version para o nó smith.

Existem definições de relacionamentos também, como pode ser visto ao se fazer node.outgoing(:knows) e algum outro nó é atribuído. Esse é o relacionamento mais comum que você pode encontrar no Neo4J, você lê da esquerda para a direita e outgoing significa de mim para outro nó; você pode também usar ingoing para definir o relacionamento oposto.

Por fim, você pode ver alguns [:since], o que significa que estou definindo propriedades para o relacionamento, como por quanto tempo Thomas Anderson conhece Trinity e Morpheus. Simples e legal! Se você desejar, você pode baixar o código completo na minha conta github.

Agora, vamos ver um problema real resolvido com Neo4J usando Ruby através do JRuby. Para economizar um tempo, eu reduzi o problema para uma forma mais simples. Imagine que você precisa ir de um estado dos EUA para outro, supondo que você pode fazer o menor caminho e pode atravessar o menor número possível de estados e atingindo seu destino final passando por menos estados. Esse é um problema típico que o Neo4J soluciona para você. Primeiramente, vamos analisar a figura.

Como eu disse, eu simplifiquei o problema, de forma que, para esse cenário, você pode viajar pelas cidades verdes – vamos dizer que essa é a política de viagem da empresa. É hora de codificar: inicialmente, eu só vou criar os nós para esse problema.

miami   = Neo4j::Node.new(:name => 'Miami'       , :state => "Florida"    , :ab => "FL")
ny = Neo4j::Node.new(:name => 'New York' , :state => "New York" , :ab => "NY")
wichita = Neo4j::Node.new(:name => 'Wichita' , :state => "Kansas" , :ab => "KS")
austin = Neo4j::Node.new(:name => 'Austin' , :state => "Texas" , :ab => "TX")
detroit = Neo4j::Node.new(:name => 'Detroit' , :state => "Michigan" , :ab => "MI")
denver = Neo4j::Node.new(:name => 'Denver' , :state => "Colorado" , :ab => "CO")
la = Neo4j::Node.new(:name => 'Los Angeles' , :state => "California" , :ab => "CA")

Eu criei todas as cidades que nós precisamos para o problema, adicionei algumas informações úteis como qual é o estado da cidade e o nome da cidade. O próximo passo é estruturar os relacionamentos seguindo a figura acima.

miami.outgoing(:to) << ny
miami.outgoing(:to) << austin
ny.outgoing(:to) << miami
ny.outgoing(:to) << detroit
detroit.outgoing(:to) << ny
detroit.outgoing(:to) << wichita
wichita.outgoing(:to) << detroit
wichita.outgoing(:to) << denver
denver.outgoing(:to) << wichita
denver.outgoing(:to) << la
austin.outgoing(:to) << miami
austin.outgoing(:to) << wichita
la.outgoing(:to) << austin
la.outgoing(:to) << denver

Cada cidade tem duas rotas para outras cidades; não existe nenhuma cidade com mais de duas rotas, no caso de você realmente precisar atravessar outras cidades para atingir seu destino. As estruturas estão prontas, vamos implementar uma função em Ruby para cobrir o caminho mais curto para a seguinte rota.

Você está em Miami e quer ir para Wichita. Eu já desenhei o caminho mais curto para ajudar a ver se o código vai funcionar. Como pode ver, existe um caminho mais longo se pegarmos NY, mas, se nós formos por Austin, alcançamos o caminho mais curto para nosso destino final em Wichita.

class PathFinder
def self.print_shortest_path(from,to)
puts "Route from #{from[:name]} to #{to[:name]}:"
it = Neo4j::Algo.shortest_path(from,to).iterator
last = from[:name]
it.next
while it.hasNext
n = it.next
if !n[:name].blank?
puts " #{last} -> #{n[:name]}"
last = n[:name]
end
end
end
end

Eu estou usando o algoritmo Neo4j::Algo.shortest_path para encontrar o caminho mais curto. Essa função já é fornecida com o Neo4J. Eu somente iterei sobre o nó dos resultados e imprimi a informação em um formato aceitável. Este código produzirá a seguinte saída.

$ jruby cities.rb
I, [2011-04-12T00:29:26.021000 #31908] INFO -- : Enable remote shell at port port=9332
Route from Miami to Wichita:
Miami -> Austin
Austin -> Wichita

Oba, está funcionando! Você pode alterar as cidades e você vai vê-lo ainda funcionando. É isso, com um código muito simples, nós conseguimos um ótimo resultado no final do dia. O que interessa é a estrutura: algumas são muito melhores para certos domínios de problemas. Você pode baixar o código fonte completo na minha conta github.

?
Texto original disponível em http://diego-pacheco.blogspot.com/2011/04/fun-with-neo4j-and-jruby.html