Roles and Users is a classic domain model well suited to representation as a directed graph. The neo4j team has provided us with a good summary of how to implement this pattern using neo4j here . Utilizing jo4neo we can also solve this problem via a combination of the neo graph database and simple java objects. The code and maven build for this example are posted here. I encourage you to run the example and browse the code. We’ll need two domain classes, one for Users and one for Roles.
public class User {
transient Nodeid neoid;
@neo(index=true) public String id;
@neo public String firstName;
@neo public String lastName;
@neo("role") public Collection directRoles;
jo4neo uses the nodeid field to map the object to the graph. Each field we’d like to persist is annotated with @neo, and some fields carry a bit more information, such as the id field where we request and index to be created. Another notable variation is the “directRoles” field. By default jo4neo uses the method’s name to name relationships in the graph. If you decide you’d like something different, then provide a value as I’ve done with the directRoles field annotation. The Role class is annotated in a similar way:
public class Role {
transient Nodeid id;
@neo public Role parent;
@neo(index=true) public String name;
@neo(inverse="parent") Collection children;
@neo(inverse="role") public Collection directMembers;
@neo(traverser=RoleMembers.class) public Collection members;
Each role is related to a parent role. The absence of a relationship is indicated by null. Next the name field is declared, and is annotated as having an index. This provides us with the ability to find this instance, should the need arise, by it’s name. The Role class introduces two new jo4neo features not seen in the User class:
- inverse relationships.
- traverser providers
First I’d like to delve a little deeper into traverser providers because they give a good example of a hybrid approach to binding objects to the neo graph. We don’t want to abstract neo behind the weaknesses of objects, and at the same time we don’t want to ignore the benefits of the object abstraction altogether. Can they co-exist?
Traverser vs Java
In the example domain model, each Role specifies if it has a parent, for example, all customer service reps are also basic users and inherit the roles and permissions of the more general group. This makes it possible to answer the question of whether or not a given user is a member of a particular role. Using strictly a strictly java centric approach we can answer role membership by creating two methods, one on the User class to iterate over explicit relations, and the one on the role class to add transitive relations to parent roles. First the User class asks each of it’s explicit roles if it’s a member of the given role:
/**
* from the User class, for each of my direct role
* relations, ask the role if I'm a member
*/
public boolean hasRole(Role r) {
for (Role role : directRoles)
if (role.hasRole(r))
return true;
return false;
}
Next, the Role class performs a transitive closer on all its parent roles. If the role is found in the direct roles, or one of their parents, then the answer is yes, the user is a member of the given role.
/**
* from the Role class, visit my parents
*/
public boolean hasRole(Role r) {
for(Role c = this;c != null && c != c.parent;c = c.parent)
if (c.equals(r))
return true;
return false;
}
jo4neo abides by the annotations and loads the parent relationships and role membership relations so that this is possible. The advantage of this approach is that it is Java object centric. It would work the same if your persistence mechanism were JPA or some other ORM provider. (Yes, indeed, the annotations themselves would have to change). If you are familiar with neo4j you’re probably thinking that this is actually a place where a traverser would help. The user/roles pattern described on the neo wiki gives us clarity on how membership is answered via a traverser. jo4neo doesn’t require you to abandon the goodness of the neo4j graph api. If fact, it helps you access it. At any given time you can ask your jo4neo objects for their id ( Nodeid.id() ) and jump straight into raw neo if you like. jo4neo provides a 3rd way, a middle road perhaps, that reaps the benefits of both the traverser pattern as well as the simplicity or familiarity of Java Objects. Notice that the Role class has a “members” field with an interesting annotation argument:
@neo(traverser=RoleMembers.class) public Collection members;
@neo(traverser=class) tells jo4neo use a particular class as a factory or provider for a traverser. Next, jo4neo takes that traverser and uses it to fill the collection with values. Additionally, jo4neo will take into account the generic type for the field, and filter nodes accordingly. Specifically, in this example the traverser will encounter roles, however, they are not reconstituted and inserted into the collection (which would of course result in a type cast exception). The requirements for a traverser provider are that it be an implementation of TraverserProvider and have a public default constructor. The example’s traverser is nearly identical to the one from neo’s wiki:
public class RoleMembers implements TraverserProvider {
public Traverser get(Node n) {
return n.traverse(
Order.BREADTH_FIRST,
StopEvaluator.END_OF_GRAPH,
ReturnableEvaluator.ALL_BUT_START_NODE,
UserRoleRelationships.role,
Direction.INCOMING,
UserRoleRelationships.parent,
Direction.INCOMING);
}
}
The java method and the traverser annotation answer the same question. This is the first example I’ve crafted with the traverser feature and you may find bugs or have ideas of interesting ways it might be applied. If so please comment here or to thewebsemantic at google.com.