Introducing Gremlex

An open source Gremlin server driver for Elixir

Also written by Sam Havens

The Gremlex

If you want to use a graph database in your Elixir application, but don’t want to write database commands as raw strings, you’re in luck. Gremlex is a lightweight, simple to use library for composing Gremlin queries. The Gremlin graph-traversal language is available on all Apache TinkerPop™- enabled graph databases, including AWS Neptune, CosmosDB, Neo4j, and OrientDB.

At CarLabs, we store our more complex conversation logic in AWS Neptune. We need to read from and write to Neptune from within our Elixir applications and we want to do this in a functional way that feels like idiomatic Elixir — and thus Gremlex was born. After a few months of using it in production, we are happy to present Gremlex to the rest of the Elixir community. It’s open-source, available on GitHub and Hex, and MIT-licensed.

Creating queries

Gremlex is easy to use if you know Gremlin and makes learning about Gremlin functions easy if you are a newcomer with the support of Elixir tools.

Let’s take a Gremlin query:

g.V().has("name","marko").out("knows").out("knows").values("name")

and turn it into a Gremlex query:

Graph.g()
|> Graph.v()
|> Graph.has("name", "marko")
|> Graph.out("knows")
|> Graph.out("knows")
|> Graph.values("name")
|> Client.query

We also support nested queries. Let’s take a more complex example by writing a nested Gremlin query

g.V(1).repeat(out()).times(2).path().by('name')

which can be represented in Gremlex as

Graph.g()
|> Graph.v(1)
|> Graph.repeat(Graph.g() |> Graph.out())
|> Graph.times(2)
|> Graph.path()
|> Graph.by("name")
|> Client.query

How does Gremlex work under the hood?

Every query and subquery begins with Graph.g() which creates an Erlang queue. Each function takes a queue and 0 or more arguments. That function updates the queue by appending a tuple, where the first value in the tuple is the Gremlin function name and the second is its arguments.

This allows Gremlex to easily build queries by passing them through a series of functions using the pipe operator.

Once we call Client.query we compile the query to the Gremlin equivalent and make a request using that Gremlin query.

This is cool, but can you just tell me exactly how to use it?

First off, to use this Gremlin server driver, you need a Gremlin server running. If you haven’t done that before, see the instructions over on the Apache TinkerPop Getting Started page.

To install Gremlex from Hex.pm:

def deps do
[
{:gremlex, "~> 0.2.0"}
]
end

You can configure Gremlex by adding the following to your config.exs:

config :gremlex,
host: "127.0.0.1",
port: 8182,
path: "/gremlin",
pool_size: 10,
secure: false

Gremlex uses confex, so that you can easily define your configuration to use environment variables when it comes time to deploying. To do so, simply have the parameters that need to be dynamically read at run time set to {:SYSTEM, "ENV_VAR_NAME"}.

Example Time

Our goal is to create a vertex with a property, create a second vertex, and create a relationship between the two vertices. The following functions are not delivered with Gremlex, but this will give you a basic idea of how to use Gremlex in Elixir.

Step 1 — Create an initial vertex:

defmodule Gremlex.Medium do
alias Gremlex.Graph
alias Gremlex.Client

def create_vertex(label) do
Graph.g()
|> Graph.add_v(label)
|> Client.query()
end
...
end
Function call to create vertex

The function create_vertex takes in a label and creates a vertex with the given label. The return is a tuple of an atom :ok or :error and a list of Gremlex Vertices, or for our specific use case, the vertex that we created.

Step 2 — Add a property

defmodule Gremlex.Medium do
...
def add_property(id, prop_name, prop_value) do
Graph.g()
|> Graph.v(id)
|> Graph.property(prop_name, prop_value)
|> Client.query()
end
...
end
Function call to create property

The function add_property takes a vertex id, a property name, and a property value. The return is a tuple of :ok and a list with our updated vertex with the new property that we added.

Step 3 — Create a vertex and create a relationship

defmodule Gremlex.Medium do
...
def create_relationship({:ok, target}, source_id, edge_label) do
Graph.g()
|> Graph.v(source_id)
|> Graph.add_e(edge_label)
|> Graph.to(hd(target))
|> Client.query()
end
...
end
Function call to create_relationship

The create_relationship function takes three parameters and pattern matches on {:ok, target}, a vertex id meant to be the source of the relationship, and a label name for the edge that will be created. In the example above, you can see that first we create a vertex and pass in the response to the create_relationship function. Since we are passing in a list of vertices as target, we are going to get the first element of the list as the target of the relationship. The return value of this function is a tuple with :ok and a list of Gremlex Edges which describes the relationship.

Hopefully these examples give you a good basis on how to utilize Gremlex in your own applications.

The Future

We at CarLabs think that Elixir and graph databases are two underutilized pieces of technology, and we believe that both will continue to grow in popularity as more people are exposed to their power and ease-of-use. If you agree, then why not try out Gremlex today? Or even better, submit a PR — it’s early days, and there are still lots of beginner-friendly issues (there are harder issues too, if that’s your style).

We’re a friendly bunch, so if you have questions, feel free to open an issue on GitHub or leave a comment here. And of course, if you’re a car manufacturer or dealer looking to transform the way you communicate with your shoppers, owners, and finance customers… we have a lot to talk about.


Finally, special thanks to Becca Lasky for the amazing logo (seriously, if you have seen both the Elixir and TinkerPop logos, you realize how perfect it is), and to GitHub/@JohannesSoots for the big PR adding nested queries 👏👏👏