Feel free to skim it or not, just don't want it to get lost so that no one else
can learn from that or contribute alternative solutions.
Cheers
Michael
1) All nodes are equal in Neo4j if I am correct, so if I have different types
myself then I just create a node_type property with a text string to
differentiate between f.ex. users and jobs. Is this the correct way to handle
this?
2) Complex data structures, I need to store complex data structures in Neo4j,
think about a whole CV like LinkedIn. When I create or update nodes can U
pass-in this complex data structure as a whole? F.ex. below here could I add a
parameter CV that contains a Hash where some values are Hashes themselves
again? Or can should I use JSON as a value?
Neography::Node.create("age" => 31, "name" => "Max")
3) I am currently creating my own models (Rails based web app) where the models
themselves are not so much used like in Rails, they are only a nice way to
collect logic in one place. F.ex. I could have Employee.create_new hash which
internally will use neography to create a new employee and return the created
one as a hash. Is this a good way to thank about the problem, or have other
patterns emerged?
1) There are several strategies, marking the nodes with certain properties is
one, others are - linking the nodes to a "category" node so that they can be
easily accessed from there, or putting them in a dedicated index for quick
lookup.
2) Nodes only have primitive properties right now. To store hashes or more
complex document structures you use other nodes and relationships between them.
That should be quite easy to put in a helper method.
i.e. if the inner property is a hash take its property name and instead of
making a property out of that one, create a relationship with this name to a
node which has the hash keys + values as properties (and recursively so).
Fortunately it is easy with ruby to just reopen the Node class and add those
convenience methods. I did that as well. (Code below)
3) I create my own Domain objects for the Neo4j-Hosting Domain and just have
them get a Neography node as intitializer param, then you can delegate all
store-methods to this node or its node.neo_server and have the other domain
code directly in the object (code also below). I'm also in the process of
exploring this whole remote-rest-store domain modelling, so we can certainly
learn a lot from each other.
Mark you can still do your uniqueness constraint of the email with the index.
I would have written the same as Jim, put the E-Mail addresses in a string
array field, but be sure to index them not as an array but individually.
Regarding your other questions of reading structures. Traversing is crazy fast
in Neo4j. So what you do is not a manual navigation over the relationships but:
- you get your start node (i.e. the one being the person) from the index or
from another traversal. (you can see that node like a root entity).
Then you specify a traversal description. I.e. which relationships to traverse,
how deep etc. And then let the neo4j traverser do the work and have you return
all the paths to the nodes that are included in your traversal - i.e. your
whole document == aggregate structure.
Then in the client you pull those paths into your domain model and have it
ready for processing/displaying whatever.
For hints for the traversal either look at the code of the previous email or
that a quick look at the neo4j-imdb-app sourcecode at github:
https://github.com/jexp/heroku-neo4j-example
Cheers
Michael
Ok one more question for modeling the data: Say a user can have multiple e-mail
addresses what is the best way to model this? Have a single property containing
a list with addresses or let
email_address be its own entity and create relations between the user and
e-mail_address? The email address will be used to search for a user f.ex. so I
rather have Neo4j being able to
determine if it already exists.
-Mark
There's nothing in Neo4j that determines this one way or another. If email
addresses are first class citizens in your domain, then they should be nodes.
Otherwise make them properties.
For instane, if you want to be able to assert that two users are actually the
same person because they share an email address, then make the email address a
node. Otherwise it's a property.
Jim
I am using e-mail address as a way to select a user, but I didn't think of it
as a first class citizen. A user can have multiple e-mail addresses and in
cases non. The thing I am not sure
about is how to select a user by e-mail address. Can Neo4j have an Array as
property type, using the ReST API and select a node if it the array contains
the filter value?
I am starting to realize that how I think about my data needs to be a bit
different from f.ex. MongoDB or CouchDB. In many ways I see great
possibilities, but need to get my head around
these fundamental things :)
-Mark
It's possible to index node properties, that gives you effectively what Mongo
does. Arrays are supported, you just pass in a JSON array:
{ "email_addresses" : ["[email protected]", "[email protected]",
"[email protected]"]}
The question you need to answer is: is there any value in the relationship
between a user and their email address(es)?
Jim
Ah cool, that is what I was wondering about how easy Neo could index properties
like an array. Because this is a border case (I might actually promote e-mail
address to a first class citizen for uniqueness check), but there are other
cases where I do not see the need to do this. Then it is good to know that Neo
will handle that easily.
How much is the ReST API behind the direct Java way?
-Mark
Regarding the second - information about those concepts is distributed over
blog posts, wiki entries and mailing list posts. We certainly need more docs
from the users/developers point of
view, e.g. on how to model your domain in a graph.
What I've just found (but that is by far not enough:
http://wiki.neo4j.org/content/Domain_Modeling_Gallery)
http://wiki.neo4j.org/content/Design_Guide
http://stackoverflow.com/questions/1000162/has-anyone-used-graph-based-databases-http-neo4j-org
http://www.build47.com/posts/neo4j-presentation-at-twitter-headquaters/
http://www.slideshare.net/directi/neo4j-and-the-benefits-of-graph-dbs-3-3325734
I also would like to discuss how one could map the DDD terms onto a graph
database and where it is fitting and where not.
Two last points: don't underestimate relationships, they are full first level
citizens and go for traversals rather than manually navigating along the
relationships.
node_ext.rb
require 'rubygems'
require 'neography'
module Neography
class Node
class << self
def create_and_index(data, to_index)
node = Neography::Node.create(data)
return node unless to_index
to_index.each do |index,names|
names.each do | prop |
node.neo_server.add_node_to_index(index,prop,data[prop],node.neo_id)
end
end
node
end
def find(index, prop, value)
res = Neography::Rest.new.get_node_index(index,prop,value)
return nil unless res
Neography::Node.load(res.first)
end
end
end
class Rest
def get_type(type)
case type
when :node, "nodes", :nodes, "nodes"
"node"
when :relationship, "relationship", :relationships, "relationships"
"relationship"
when :path, "path", :paths, "paths"
"path"
when :fullpath, "fullpath", :fullpaths, "fullpaths"
"fullpath"
else
"node"
end
end
end
end
domain.rb
require 'node_ext'
# todo I want to be able to filter for properties with the REST api w/o
reverting to JS
module Gateway
class Provider
def initialize(node)
@node = node
end
def to_s
"#{@node.name} (#{@node.neo_id})"
end
class << self
def get(name)
node = Neography::Node.find(:providers,:name,name)
return nil unless node
Provider.new(node)
end
def add(name)
provider = Provider.get(name)
return provider if provider
node = Neography::Node.create_and_index({:name => name}, :providers =>
[:name])
return nil unless node
Provider.new(node)
end
end
def plans
@node.outgoing(:active_plan).collect
end
def plan(name)
plans.find { |plan| plan.name == name }
end
def users
rels.incoming(:account).collect
end
def userFor(account)
users = @node.incoming(:account).filter(" position.lastRelationship() !=
null && position.lastRelationship().getProperty('account') == '#{account}';")
return User.new(users.first) if users && !users.empty?
user = Neography::Node.create_and_index({:name => account, :email =>
account}, :users => [:email, :name ])
@node.neo_server.create_relationship(:account, user, @node, {:account =>
account })
User.new(user)
end
def new_hash(size = 8)
Digest::SHA1.hexdigest(rand.to_s)[0..size]
end
def databaseFor(user, plan)
begin
id = new_hash
end while !Node.find(:databases, :id, id)
db = Neography::Node.create_and_index({
:id => id, :login => new_hash, :password => new_hash,
:state => :creating
}, :databases => [:id])
user.outgoing(:active_database) << db
db.outgoin(:has_plan) << plan
Database.new(db)
end
end
end
_______________________________________________
Neo4j mailing list
[email protected]
https://lists.neo4j.org/mailman/listinfo/user