Compilers hate him! Discover this one weird trick with Neo4j stored procedures

As you probably already know, Neo4j 3.0 finally comes with stored procedures (let’s call them sprocs from now on).

The cool thing about this is you can directly interact with sprocs in Cypher, as Michael Hunger explains in this blog post.

Writing stored procedures

During the preparation of my Neo4j introduction talk in the latest Criteo summit (we’re hiring!), I started playing around with sprocs.

The process is quite simple:

  1. You write some code, annotate it

  2. test it with the test harness

  3. package the JAR and deploy it to your Neo4j instance (plugins/)!

Actually, step 3 may repeat itself quite a few times, Neo4j sprocs must comply to a few rules before your Neo4j server accepts to deploy it.

Sproc rules

The rules are detailed in @org.neo4j.procedure.Procedure javadoc, but we can summarize them as follows:

  • a sproc is a method annotated with @org.neo4j.procedure.Procedure

  • it must return a java.util.stream.Stream<T> where T is a user-defined record type

  • the record type must define public fields

  • these can only be of restricted types

  • if the sproc accepts parameters, they all must be annotated with @org.neo4j.procedure.Name

  • parameters can only be of specific types

  • the procedure name must be unique (name = package name+method name)

  • injectable types (GraphDatabaseService et al) must target public non-static, non-final, @Context-annotated fields

Fortunately, folks at Neo Technology have done a wonderful job at error reporting. Neo4j fails fast if any of the rules is violated and gives a detailed error message.

Here is an example with Neo4j 3.0.3 and the following failing attempt to deploy the following sproc:

@Procedure
public Stream<MyRecord> doSomething(Map<String, Integer> value) {
    // [...]
}

The following error will be prompted (see logs/neo4j.log):

Caused by: org.neo4j.kernel.api.exceptions.ProcedureException: Argument at position 0 in method `doSomething` is missing an `@Name` annotation.
Please add the annotation, recompile the class and try again.

Nice error message! Just add the missing @Name on the only parameter, re-compile, package and deploy the JAR again, restart Neo4j and you’re done!

Can we do better?

The previous example is quite trivial, but this back-and-forth could be potentially repeated many times, especially when one is not much familiar with sprocs.

Fortunately for us, most of the errors can be caught at compile time.

@Eureka("annotation processing FTW!")

Annotations have been around in Java since end of 2004 (v1.5) and have come together with apt (now built in javac), the annotation processing tool.

What the latter does in brief (in long, read the spec) is to allow user-defined code to introspect a Java program at compile-time (original paper here) and possibly:

  • issue compilation notices/warnings/errors

  • generate static, source and/or bytecode files

(By the way, this means exceptions can be raised at compile-time too!)

Based on this, I decided to write a little annotation processor on my way back from Criteo summit (did I mention we are hiring?).

neo4j-sproc-compiler is born. And it’s used!

If Michael is happy, I am happy:

michael sproc compiler feedback.png

(I swear it’s not photoshopped, see #apoc channel, 1st of July 2016 in Neo4j-Users Slack).

neo4j-sproc-compiler in action

While the following screencast features Maven, the annotation processor is actually agnostic of any build tool. You can use any build tool you want or directly javac if that floats your boat!

Conclusion

Be cautious, most but not all checks can be performed at compile time. You’ll still need to write some tests and monitor your deploys!

Hopefully, this little utility that I wrote will shorten your development feedback loop and get your stored procedures harder, better, stronger and faster.

comments powered by Disqus