Domain modeling with Spring Data Neo4j

Hi all, Willie here. Last time I told you that I’m building the Skybase CMDB using Neo4j and Spring Data Neo4j, and I was excited to get a lot of positive feedback about that. I showed a little code but not that much. In this post I’ll show you how I’m building out the person configuration item (CI) in Skybase using Spring Data Neo4j.

PERSON CI REQUIREMENTS

We’re going to start really simply here by building a person CI. It’s useful to have people in the CMDB for various reasons: they allow you to define fine-grained access controls (e.g., Jim can deploy such-and-such apps to the development environment; Eric can deploy whatever he wants wherever he wants; etc.); they allow you to define groups who will receive notifications for critical events and incidents; etc.

Our person CI will have a username, first and last names, some phone numbers, an e-mail address, a manager, direct reports and finally projects he or she works on. We need to be able to display people in a list view, display a given person in a details view, allow users to create, edit and delete people and so on. Here for example is what the list view will look like, at least for now:

And here’s how our details view will look:

The relationship between a person and a project has an associated role. This relationship is also the basis for the list of collaborators: two people are collaborators if there’s at least one project of which they’re both members.

Our simple requirements should be enough to show what it feels like to write Spring Data Neo4j code.

CREATE THE PERSON AND PROJECTMEMBERSHIP ENTITIES

First we’ll create the Person. I’ve suppressed the validation and JAXB annotations since they’re irrelevant for our current purposes:

There are lots of annotations we’re using to put a structure in place. Let’s start with nodes and their properties. Then we’ll look at simple relationships between nodes. Then we’ll look at so-called relationship entities, which are basically fancy relationships. First, here’s an abstract representation of our domain model:

Now let’s look at some details.

Nodes and their properties. When we have a node-backed entity, first we annotate it with the @NodeEntity annotation. Most of the simple node properties (i.e., properties that aren’t relationships to other nodes) come along for the ride. Notice that I didn’t have to annotate firstName, lastName, email, and so forth. Spring Data Neo4j will handle the mapping there automatically.

There are a couple of exceptions though. The first one is that I put @GraphId on my id property. This tells Spring Data Neo4j that this is an identifier that we can use for lookups. The other one is the @Indexed annotation, which (surprise) creates an index for the property in question. This is useful when you want an alternative to ID-based lookup.

Now we’ll look at relationships. Speaking broadly, there are simple relationships and more advanced relationships. We’ll start with the simple ones.

Simple relationships. At a low level, Neo4j is a graph database, so we can talk about the graph in graph theoretical terms like nodes, edges, directed edges, DAGs and all that. But here we’re using graphs for domain modeling, so we interpret low-level graph concepts in terms of higher-level domain modeling concepts. The language that Spring Data Neo4j uses is “node entity” for nodes, and “relationships” for edges.

Our Person CI has a simple relationship, called REPORTS_TO, that relates people so we can model reporting hierarchies. Person has two fields for this relationship: manager and directReports. These are opposite sites of the same relationship. We use @RelatedTo(type = “REPORTS_TO”) to annotate these fields. The annotation has a direction element as well, whose default value is Direction.OUTGOING, which means that “this” node is the edge tail. That’s why we specify direction = Direction.INCOMING explicitly for the directReports field.

What’s this look like in the database? Neoclipse reveals all. Here are some example reporting relationships (click the image for a larger view):

(Small aside: there’s a @Fetch annotation–we’ll see it in a moment–that tells Spring Data Neo4j to eager load a related entity. For some reason I’m not having to use it for the manager and direct reports relationships, and I’m not sure why. If somebody knows, I’d appreciate the explanation.)

Relationship entities. Besides the REPORTS_TO relationship between people, we care about the MEMBER_OF relationship between people and projects. This one’s more interesting than the REPORTS_TO relationship because MEMBER_OF has an associated property–role–that’s analogous to adding a column to a link table in a RDBMS, as I mentioned in my reply to Brig in the last post. The Person.memberOf() method provides a convenient way to assign a person to a project using a special ProjectMembership “relationship entity”. Here’s the code:

ProjectMembership, like Person, is an entity, but it’s a relationship entity. We use @RelationshipEntity(type = “MEMBER_OF”) to mark this as a relationship entity, and as with the Person, we use @GraphId for the id property. The @StartNode and @EndNode annotations indicate the edge tail and head, respectively. @Fetch tells Spring Data Neo4j to load the nodes eagerly. By default, Spring Data Neo4j doesn’t eagerly load relationships since risks loading the entire graph into memory.

I noted in the last post that all we need to do is extend the GraphRepository interface; Spring Data generates the implementation automatically.

For findByUsername(), Spring Data can figure out what the intended query is there. For the other two queries, we use @Query and the Cypher query language to specify the desired result set. The {0} in the queries refers to the finder method parameter. In the findCollaborators() query, we use [:MEMBER_OF] to indicate which relationship we want to follow. These return Sets instead of Iterables to eliminate duplicates.

CREATE THE WEB CONTROLLER

We won’t cover the entire controller here, but we’ll cover some representative methods. Assume that we’ve injected a PersonRepository into the controller.

We have to do some work to get the Iterable that PersonRepository.findAll() returns into the format we want. IteratorUtil, which comes with Neo4j (org.neo4j.helpers.collection.IteratorUtil), helps here.

Finding a single person. Here we want to display the personal details we built out above. As with findAll(), we have to do some of the massaging ourselves:

Neo4j has a basic POJO-based mapping model and an advanced AspectJ-based mapping model. In this blog post we’ve been using the basic POJO-based approach, so we don’t need to include AspectJ-related configuration like <context:spring-configured>.

There you have it–a Person CI backed by Neo4j. Happy coding!

To see the code in more detail, or to get involved in Skybase development, please see the Skybase GitHub site.

Newsletter

Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

Email address:

Join Us

With 1,043,221 monthly unique visitors and over 500 authors we are placed among the top Java related sites around. Constantly being on the lookout for partners; we encourage you to join us. So If you have a blog with unique and interesting content then you should check out our JCG partners program. You can also be a guest writer for Java Code Geeks and hone your writing skills!

Disclaimer

All trademarks and registered trademarks appearing on Examples Java Code Geeks are the property of their respective owners. Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries. Examples Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.