seancheatham / scala-graph

A collection of Scala graph libraries and adapters for graph databases.

GitHub

Graph library/interface with adapter(s), written in Scala

Build Status

Overview

This library serves two purposes:

  1. Provide a common abstraction for accessing and manipulating a graph data structure
  2. Provide adapters for various databases (particularly graph databases)

I am a one-man show, so at best, what you see here is work I need in side projects. I've open-sourced this library because other people may find some of it useful.

My current focus is on providing an abstraction for the Neo4j graph database. As such, I have provided a common interface for accessing either an embedded database, or a remote/production instance.

Usage

This library is written in Scala. It might interoperate with other JVM languages, but I make no guarantees.

Include the library in your project.

In the build.sbt file located in your project root:

// The core definitions library:
libraryDependencies += "com.seancheatham" %% "graph-core" % "0.0.2"

// To run a basic, in-memory graph:
libraryDependencies += "com.seancheatham" %% "graph-memory-adapter" % "0.0.2"

// To connect to a Neo4j graph:
libraryDependencies += "com.seancheatham" %% "graph-neo4j-adapter" % "0.0.2"

// To expose a graph as an HTTP server:
libraryDependencies += "com.seancheatham" %% "graph-akka-layer" % "0.0.2"

// To connect to an expopsed HTTP graph server:
libraryDependencies += "com.seancheatham" %% "graph-akka-adapter" % "0.0.2"

// To connect to an HBase graph:
libraryDependencies += "com.seancheatham" %% "graph-hbase-adapter" % "0.0.2"

// To connect to a BigTable graph:
libraryDependencies += "com.seancheatham" %% "graph-big-table-adapter" % "0.0.2"

// To connect to a Document Storage graph:
libraryDependencies += "com.seancheatham" %% "graph-document-storage-adapter" % "0.0.2"

Create a Graph instance

Create a mutable in-memory graph:

import com.seancheatham.graph.adapters.memory.MutableGraph
val graph =
    new MutableGraph()

Create an immutable in-memory graph:

import com.seancheatham.graph.adapters.memory.ImmutableGraph
val graph =
    ImmutableGraph()()

Create an embedded Neo4jGraph:

// Create a temporary Neo4j graph
import com.seancheatham.graph.adapters.neo4j._
val graph = 
    Neo4jGraph.embedded()
    
// Create a graph which persists to disk
import com.seancheatham.graph.adapters.neo4j._
val graph = 
    Neo4jGraph.embedded("/path/to/save/to")

Connect to a remote Neo4j Instance

import com.seancheatham.graph.adapters.neo4j._

val address = 
    "bolt://192.168.?.?"
    
    
// If auth is required
import org.neo4j.driver.v1.AuthTokens

val auth = 
    AuthTokens.basic("username", "password")
    
val graph = 
    Neo4jGraph(address, auth)
    
    
// If auth is not required
val graph =
    Neo4jGraph(address)

Create a node

import play.api.libs.json._

val node1: Node = 
    graph.addNode("label", Map("name" -> JsString("potato")))
// NOTE: The graph created previously may not be the same graph as `node1.graph`
// Depending on the implementation of the Graph, a brand new graph may be created
// after each change to it.  To be safe, once you modify `graph`, throw it out.
// Generally, mutable graphs will re-use the same Graph for each change.

Get a node by ID

val alsoNode1: Option[Node] = 
    graph.getNode("1")
    // OR, to be safe (see above)
    node1.graph.getNode("1")

Get nodes by label and/or data

val nodes: TraversableOnce[Node] = 
    graph.getNodes(Some("label"), Map("name" -> JsString("potato")))

Create an edge between two nodes

val edge1: Edge =
    graph.addEdge(node1, node2, "edge_label", Map("weight" -> Json.toJson(1.5)))

// Or you can use some syntactic sugar:
import com.seancheatham.graph.Edge.NodeEdgeSyntax
val edge1: Edge =
    graph.addEdge(node1 -"LABEL"-> node2, Map("weight" -> Json.toJson(1.5)))

Fetch inbound or outbound edges for a node

val incomingEdges: TraversableOnce[Edge] =
    graph.getIngressEdges(node1)
    
val outgoingEdges: TraversableOnce[Edge] =
    graph.getEgressEdges(node1)

Update a node/edge

val updatedNode1 =
    graph.updateNode(node1)("name" -> JsString("carrot"), "category" -> JsString("vegetable"))
    
val updatedEdge1 =
    graph.updateEdge(edge1)("weight" -> Json.toJson(2.3))

Expose a Graph as an Akka-backed HTTP Server

The graph-akka-layer module allows you to expose a Graph through a REST API Server, backed by Akka.

From an existing Graph instance

import com.seancheatham.graph.akka.http.HttpServer
val graph: Graph =
    ???
val server =
    HttpServer(graph) // OR HttpServer(graph, "localhost", 8080)

// Visit http://localhost:8080 for API paths and details

...
// Don't forget to shut it down
server.shutdown()

Run as an Application via main method

Running the Application with no arguments will start a new mutable graph instance, bound to localhost:8080.

You can run the server using command line arguments (run -help for info), but the preferred way is using Typesafe configurations. In your application.conf file:

graph {
    http {
        host = "localhost"
        port = 8080
    }
    
    type = "mutable" // OR: immutable, neo4j-embedded, neo4j-remote
    
    // If graph.type == "neo4j-embedded"
    neo4j {
        embedded {
            dir = "/tmp/neo4jembedded"
        }
    }
    
    // If graph.type == "neo4j-remote"
    neo4j {
        remote {
            address = "bolt://127.0.0.2"
            user = "neo4j"
            password = "neo4j"
        }
    }
}

Once configured, just run the graph-akka-layer's "main()" method.