For one project we use a Gremlin DB in Azure (Cosmos DB). Various libraries are available for use. Unfortunately, no REST is supported, which is why a corresponding simple interface had to be provided here.

The most elegant way would be to set up an Azure API Management and provide a REST API. The request could then be converted internally with policies (C # code) and sent to the Cosmos DB. Unfortunately, no additional libraries can be loaded in the APIM, only a few standard libraries can be used.

Therefore, an Azure Function was created that receives a POST request with a Gremlin Query. The idea was described here by Ritu Raj, for example. However, the following adjustments were made during implementation:

  • the project was implemented as a remote-container project in Visual Studio Code and the Tinkerpop Console was also installed
  • some bugs in the code have been fixed and add calls have been prohibited

The source code with the instructions for configuration and use is stored in the Github project AzGremlinRestApi.

Using Remote-Container in VS Code

With the remote-container extension the development environment with all dependencies and libraries is relocated to a Docker container. The VS runs locally and uses all data and the mounted source code from the container. Therefore every developer has the same environment in an extremely short time.

By activating remote-container a folder .devcontainer is created (2). I have selected (1) Azure Functions & C# (.NET Core 3.1) as the development container (therefore mcr.microsoft.com/azure-functions/dotnet:3.0-dotnet3-core-tools is used as the base image in the Dockerfile). If the project is restarted, the extension recognizes the folder and the configuration and offers to reopen in the container:

The Dockerfile has been adapted accordingly to configure the access to Gremlin. These properties are used in the function and also correspond to the environment variables that would be set by the Function App Service:

# config for function
ENV GRAPHDBHOSTNAME=<COSMOSDBACCOUNT>.gremlin.cosmos.azure.com
ENV GRAPHDBKEY=<KEY>
ENV GRAPHDBNAME=<COSMOSDB>
ENV GRAPHDBCOLL=<GRAPHCOLLECTION>

To install the Tinkerpop Console, the Dockefile contains the following lines:

# install java
RUN sudo apt update && sudo apt install -y default-jdk

# install tinkerpop gremlin console in /workspaces/tinkerpop-cmd
WORKDIR /workspaces
RUN sudo chmod 777 -R /workspaces/ \
    && mkdir tinkerpop-cmd \
    && wget https://apache.mivzakim.net/tinkerpop/3.4.8/apache-tinkerpop-gremlin-console-3.4.8-bin.zip \
    && unzip apache-tinkerpop-gremlin-console-3.4.8-bin.zip \
    && mv apache-tinkerpop-gremlin-console-3.4.8/* tinkerpop-cmd/ \
    && rmdir apache-tinkerpop-gremlin-console-3.4.8

The Tinerkerpop Console can also be used to send Gremlin queries via the shell. This is particularly good for checking the query results beforehand. The configuration is defined in the remote-security.yaml file.

Azure Function for calling Gremlin Cosmos DB

The source code is pretty simple. The parameters are loaded (line 3-7) and the GremlinServer and GremlinClient objects are created (line 30-33). Before this, it is checked in line 21 that a query has been transmitted in the POST body and that this query does not try to create a new node or new edge.

In the original POST, the resultSet was looped through and only the last value was returned. Here the entire resultSet is returned directly (line 36-37) as JSON.

public static class gremgraphapi
{
	private static string hostname = Environment.GetEnvironmentVariable("GRAPHDBHOSTNAME"); 
	private static int port = 443;
	private static string authKey = Environment.GetEnvironmentVariable("GRAPHDBKEY");
	private static string database = Environment.GetEnvironmentVariable("GRAPHDBNAME");
	private static string collection = Environment.GetEnvironmentVariable("GRAPHDBCOLL");

	[FunctionName("gremlinquery")]
	public static async Task<IActionResult> Run(
		[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
		ILogger log)
	{
		log.LogInformation("C# HTTP trigger function processed a request.");
		string query="";
		if (req.Method.ToLower() == "post")
		{
		   query = JObject.Parse(await new StreamReader(req.Body).ReadToEndAsync())?["query"]?.ToString();
		}

		if (String.IsNullOrEmpty(query) || (query.Contains("add")))
			return new BadRequestObjectResult("Please pass a read query in the POST body");

		log.LogInformation($"Query: {query}");
		log.LogInformation($"hostname: {hostname}");
		string connection = "/dbs/" + database + "/colls/" + collection;
		log.LogInformation($"username: {connection}");
		log.LogInformation($"key: {authKey.Substring(0, 4)}...{authKey.Substring(authKey.Length-4, 4)}");
		string jRes="";
		var gremlinServer = new GremlinServer(hostname, port, enableSsl: true,
											username: connection,
											password: authKey);
		using (var gremlinClient = new GremlinClient(gremlinServer, new GraphSON2Reader(), new GraphSON2Writer(), GremlinClient.GraphSON2MimeType))
		{
			// Create async task to execute the Gremlin query.
			var resultSet = gremlinClient.SubmitAsync<dynamic>(query).Result;
			jRes = JsonConvert.SerializeObject(resultSet).ToString();
		}
		return new OkObjectResult(jRes);
	}
}