Neo4j Drivers, version 1.7
A few weeks ago, we released our 1.7 official Neo4j Drivers. For the first time, this covered five languages, namely Java, .Net, Python, JavaScript and Go. Here is what’s in the box.
To read more about the new Go driver, have a look at this post.
The focus of this article is the 1.7 Drivers where we’ll look at the new features in detail. The highlights are as follows:
- Transaction Config
- Custom Server Address Resolver
- Server Name Indication (SNI) and hostname verification
We added a few new classes and methods in the API to support these new features. But other than this, the surface remains the same. If you’re running Neo4j 3.3 or above, you can upgrade to a 1.7 driver without any changes to your code.
Transaction Config
Transaction Config enables fine-grained configuration control on any transaction started by a driver. It provides two settings: transaction timeout and transaction metadata.
Transaction timeout defines the maximum time for which a transaction should run. It can be used to kill overrunning transactions in order to free database resources, thereby protecting the database from overload. By default (i.e. without a transaction timeout specified explicitly on transaction start) the transaction timeout defined in neo4j.conf at dbms.transaction.timeout will be used.
Transaction metadata, on the other hand, is often used along with the database logs for auditing. When transaction metadata is attached to a transaction, this will appear alongside the transaction in Neo4j query.log and in the output from the dbms.listTransactions procedure. This could be used to attach information about the end users, for example. Note that as query.log and dbms.listTransactions are Neo4j Enterprise features, these will only be available to Enterprise database users.
The transaction configuration can be provided when a transaction is created using the drivers.
In Java, a TransactionConfig object can be created with a builder:
Map<String,Object> metadata = new HashMap<>();
metadata.put( "Username", myUsername );
metadata.put( "Time", ZonedDateTime.now() );TransactionConfig config = TransactionConfig.builder()
.withMetadata( metadata )
.withTimeout( Duration.ofSeconds( 1 ) )
.build();
This configuration can then be passed to a transaction on creation:
try ( Transaction tx = session.beginTransaction( config ) )
{
StatementResult result = tx.run(
"CREATE (a:Greeting) SET a.message = $message " +
"RETURN a.message + ', from node ' + id(a)",
Values.parameters( "message", message ) );
return result.single().get( 0 ).asString();
}
It can also be attached directly to Cypher execution code:
session.run(
"CREATE (a:Greeting) SET a.message = $message " +
"RETURN a.message + ', from node ' + id(a)",
Values.parameters( "message", message ), config );
and:
session.writeTransaction( tx -> {
StatementResult result = tx.run(
"CREATE (a:Greeting) SET a.message = $message " +
"RETURN a.message + ', from node ' + id(a)",
Values.parameters( "message", message ) );
return result.single().get( 0 ).asString();
}, config );
This functionality is supported in all drivers, so if you aren’t using Java, head over to our Driver Manual and Driver API Documentation to find an example for your language.
Note also that you’ll have to be running at least Neo4j 3.5 to be able to use TransactionConfig, since the mechanism requires Bolt version 3.
Custom Server Address Resolver
The custom server address resolver allows a user to specify an address resolver function which is used by the routing driver when resolving the initial cluster address passed in a bolt+routing URI.
Inside the Driver object is a routing table which maintains a set of servers to which the client can connect. The initial population of this table can therefore be influenced by using a function that returns multiple initial server addresses, avoiding the need for complex DNS configuration or a separate load balancer above the cluster core members.
The resolution process happens:
- during the very first rediscovery when the driver is created,
- when all the known routers from the current routing table have failed and the driver needs to fallback to the initial address.
In other words, this address resolver only kicks in when the driver cannot perform a discovery using its rediscovery servers in its current routing table. Then it will fall back to this address resolver to retrieve more hints about where to perform rediscovery.
The example below shows a possible implementation in Java:
String virtualUri = "bolt+routing://x.acme.com";
Config config = Config.builder()
.withResolver( address -> new HashSet<>( Arrays.asList(
ServerAddress.of( "a.acme.com", 7676 ),
ServerAddress.of( "b.acme.com", 8787 ),
ServerAddress.of( "c.acme.com", 9898 ) ) ) )
.build();return GraphDatabase.driver( virtualUri, AuthTokens.basic( user, password ), config );
Some drivers such as Python driver also support this feature for a direct driver (i.e. one created via the bolt URI scheme). The details for each language driver can be found in the driver’s API documentations.
SNI and hostname verification
SNI (Server Name Indication) is an extension to TLS. It allows the client to pass the hostname that the client intends to talk to as part of the TLS handshake negotiation. This can be used (for example) by a proxy server to route traffic arriving at a single IP address to multiple backend servers. In 1.7 drivers, we have enabled SNI on the client side by default to make integration easier for systems that require SNI support.
We have also provided support for verification of the hostname in the server certificate received by the client. So we not only verify that the server is a valid server (with a valid certificate) but we also ensure that the server is the one that the client intended to talk to (whose hostname in the CN field of the server certificate matches the host part in the client’s connecting request URL).
The following code illustrates how to enable hostname verification with neo4j-java-driver via driver configuration (which is turned off by default):
Config config = Config.builder()
.withEncryption()
.withTrustStrategy(
TrustStrategy.trustCustomCertificateSignedBy(certFile)
.withHostnameVerification() )
.build();driver = GraphDatabase.driver( uri,
AuthTokens.basic( user, password ), config );
Conclusion
So you can see that the 1.7 drivers provide a good set of extra features. Don’t forget that we have four other languages available, aside from Java — Python, JavaScript, .NET and Go. All languages share a uniform API, so it shouldn’t be too hard to map these same concepts into our other supported languages. Examples can be found in our documentation.
If you run into any issues while using our drivers, please feel free to find us on our Neo4j community site or open a GitHub issue.