Saturday, December 31, 2011

Graph Analysis with Scala and Spring Data Neo4j

Neo4j graph database is the an awesome technology for graph data persistence. With Spring Data Neo4j accessing graph data becomes even easier.

The easiest way to use Spring Data Neo4j is by enabling its AspectJ weaving. Because I wanted to use both Scala and Spring Data Neo4j in my web application, using both languages (AspectJ and Scala) in the same project isn't possible for now. A tip for you, separate the AspectJ-enhanced classes (@NodeEntity, @RelationshipEntity, and repositories) into a separate AspectJ project, then depend on the AspectJ from the Scala application.

The result is I'm able to fully utilize the versatile Scala and the excellent Spring Data libraries with Neo4j.

Now for mandatory code comparison between Java and Scala :-)

Java programming language version:

package com.satukancinta.web;

import java.util.List;
import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.neo4j.conversion.Result;
import org.springframework.data.neo4j.support.Neo4jTemplate;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.satukancinta.domain.User;

/**
 * @author ceefour
 * Various analysis of the social graph.
 */
@Named @ApplicationScoped
public class GraphAnalysis {
   
    private transient Logger logger = LoggerFactory.getLogger(GraphAnalysis.class);
    @Inject Neo4jTemplate neo4j;
   
    public List<MutualLikeRow> getMutualLikesLookup(User user) {
        logger.debug("getMutualLikesLookup {}/{}", user.getNodeId(), user);
        Result<Map<String, Object>> rows = neo4j.query("START x=node("+ user.getNodeId() +") MATCH x-[:LIKE]->i<-[:LIKE]-y RETURN id(y) AS id, y.name AS name, COUNT(*) AS mutualLikeCount", null);
        Iterable<MutualLikeRow> result = Iterables.transform(rows, new Function<Map<String, Object>, MutualLikeRow>() {
            @Override
            public MutualLikeRow apply(Map<String, Object> arg) {
                return new MutualLikeRow((Long)arg.get("id"), (Integer)arg.get("mutualLikeCount"),
                        (String)arg.get("name"));
            }
        });
        return Lists.newArrayList(result);
    }

}

Scala programming language version:

package com.satukancinta.web
import collection.JavaConversions._
import org.slf4j._
import javax.inject.Inject
import org.springframework.data.neo4j.support.Neo4jTemplate
import com.satukancinta.domain.User
import javax.enterprise.context.ApplicationScoped
import javax.inject.Named

/**
 * @author ceefour
 * Analysis functions of friend network graph.
 */
@Named @ApplicationScoped
class GraphAnalysis {

  private lazy val logger = LoggerFactory.getLogger(classOf[GraphAnalysis])
  @Inject private var neo4j: Neo4jTemplate = _
 
  def getMutualLikesLookup(user: User): java.util.List[MutualLikeRow] = {
    logger.debug("getMutualLikesLookup {}/{}", user.getNodeId, user)
    val rows = neo4j.query(
        "START x=node("+ user.getNodeId() +") MATCH x-[:LIKE]->i<-[:LIKE]-y RETURN id(y) AS id, y.name AS name, COUNT(*) AS mutualLikeCount", null)
    val result = rows.map( r =>
          new MutualLikeRow(r("id").asInstanceOf[Long],
            r("mutualLikeCount").asInstanceOf[Integer].longValue,
            r("name").asInstanceOf[String]) )
        .toList
    result.sortBy(-_.mutualLikeCount)
  }
 
}


As you can see, the Scala version is not only much more concise, easier to understand, but actually has added functionality (sorted using .sortBy) with less code. Thanks to collection functions and closure support.

To learn more about Scala programming, I recommend Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.

No comments:

Post a Comment