Quarkus ArangoDB Client Extension
ArangoDB is a scalable graph database system to drive value from connected data, faster. Native graphs, an integrated search engine, and JSON support, via a single query language.
This extension has been developed following samples presents in ArangoDB Java Driver |
Installation
If you want to use this extension, you need to add the io.quarkiverse.arangodb-client-ext:quarkus-arangodb-client-ext
extension first to your build file.
For instance, with Maven, add the following dependency to your POM file:
<dependency>
<groupId>io.quarkiverse.arangodb-client-ext</groupId>
<artifactId>quarkus-arangodb-client-ext</artifactId>
<version>1.0.1</version>
</dependency>
Configuring
The Arangodb driver can be configured with standard Quarkus properties.
Connection
Configuration support multiple hosts. If you have only one host you can default empty host naming can be used.
-
Single Host configuration
src/main/resources/application.propertiesquarkus.arangodb.hosts.hostname = localhost quarkus.arangodb.hosts.port = 8529
src/main/resources/application.propertiesquarkus.arangodb.hosts.host1.hostname = host1 quarkus.arangodb.hosts.host1.port = 8529 quarkus.arangodb.hosts.host2.hostname = host2 quarkus.arangodb.hosts.host2.port = 8529
Authentication
Standard username / password authentication
quarkus.arangodb.user = root
quarkus.arangodb.password = password
JWT authentication
ArangoDB Java Driver provide JWT based authentication. This kind of authentication is not implemented because no use case has been determined.
SSL secure connection
To activate ssl secure connection please add these properties:
quarkus.arangodb.use-ssl = true
quarkus.arangodb.ssl-truststore.location = example.truststore
quarkus.arangodb.ssl-truststore.password = 12345678
The truststore muse be generated using the PEM certificate used on Arangodb server side. The truststore location can refer to resources folder or on an external one. It is preferable to use an external one to better handle certificate renewal or revocation without repackaging the application. |
If you want to add, setup SSL using a PEM certificate on Arangodb side you could have a look to ArangodbContainer inside this project. It can give you good insights for producing custom container image regarding embedding certificate and updating arangodb.conf . You can easily produce an image based on official arangodb image and use /docker-entrypoint-initdb.d/ folder to add your custom shell or javascript scripts.
|
PEM certificate and example.truststore using password 12345678 used in this extension are coming from the ArangoDB Java Driver
|
Dev Services
Quarkus supports a feature called Dev Services that allows you to create various datasources without any config.
Dev Services will bring up an Arangodb container if you didn’t explicit add the default values or configured custom values for any of quarkus.arangodb.hosts.*
, quarkus.arangodb.user
or quarkus.arangodb.password
.
Otherwise, Quarkus will automatically start an Arangodb container when running tests or dev-mode, and automatically configure the connection.
When running the production version of the application, the Arangodb connection need to be configured as normal, so if you want to include a production database config in your application.properties
and continue to use Dev Services we recommend that you use the %prod.
profile to define your Neo4j settings.
Customization
For the following paragraph the Person
object will be used as sample to describe many ways to use, implement custom serializer / deserializer.
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public final class Person {
private final String name;
@JsonCreator
public Person(@JsonProperty("name") final String name) {
this.name = Objects.requireNonNull(name);
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
Serializer / Deserializer
By default, if jackson is not added as a dependency in the application, the shaded Jackson version present inside the driver will be used. In this case the configuration will be automatic. It works fine, however custom ObjectMapper configuration is not supported.
To define custom Jackson configuration you can do it in several ways.
-
Using Quarkus jackson
Add this dependency in pom.xml
pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-jackson</artifactId> </dependency>
Next you can define a custom
ObjectMapperCustomizer
. This customizer is common across all the application.import java.io.IOException; import jakarta.inject.Singleton; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.module.SimpleModule; import io.quarkus.jackson.ObjectMapperCustomizer; @Singleton public final class RegisterCustomModuleCustomizer implements ObjectMapperCustomizer { private static final String PERSON_SERIALIZER_ADDED_PREFIX = "MyNameIs"; private static final String PERSON_DESERIALIZER_ADDED_PREFIX = "Hello"; @Override public void customize(final ObjectMapper mapper) { final SimpleModule module = new SimpleModule("PersonModule"); module.addDeserializer(Person.class, new PersonDeserializer()); module.addSerializer(Person.class, new PersonSerializer()); mapper.registerModule(module); } private static class PersonSerializer extends JsonSerializer<Person> { @Override public void serialize(final Person value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException { gen.writeStartObject(); gen.writeFieldName("name"); gen.writeString(PERSON_SERIALIZER_ADDED_PREFIX + value.getName()); gen.writeEndObject(); } } private static class PersonDeserializer extends JsonDeserializer<Person> { @Override public Person deserialize(final JsonParser parser, final DeserializationContext ctx) throws IOException { final JsonNode rootNode = parser.getCodec().readTree(parser); final JsonNode nameNode = rootNode.get("name"); final String name; if (nameNode != null && nameNode.isTextual()) { name = PERSON_DESERIALIZER_ADDED_PREFIX + nameNode.asText(); } else { name = null; } return new Person(name); } } }
-
Custom ArangodbSerde
If you want to define ObjectMapper behaviors only for Arangodb you can do it by providing a custom
ArangoSerde
. Only one can be defined on the application.import static com.fasterxml.jackson.databind.DeserializationFeature.USE_BIG_INTEGER_FOR_INTS; import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED; import java.io.IOException; import jakarta.enterprise.inject.Produces; import jakarta.inject.Singleton; import com.arangodb.ContentType; import com.arangodb.serde.ArangoSerde; import com.arangodb.serde.jackson.JacksonSerde; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.module.SimpleModule; @Singleton public final class CustomArangodbSerde { private static final String PERSON_SERIALIZER_ADDED_PREFIX = "MyNameIs"; private static final String PERSON_DESERIALIZER_ADDED_PREFIX = "Hello"; @Singleton @Produces public ArangoSerde arangodbSerdeProducer() { return JacksonSerde.of(ContentType.JSON) .configure(mapper -> { mapper.configure(WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, true); mapper.configure(USE_BIG_INTEGER_FOR_INTS, true); final SimpleModule module = new SimpleModule("PersonModule"); module.addDeserializer(Person.class, new CustomArangodbSerde.PersonDeserializer()); module.addSerializer(Person.class, new CustomArangodbSerde.PersonSerializer()); mapper.registerModule(module); }); } private static class PersonSerializer extends JsonSerializer<Person> { @Override public void serialize(final Person value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException { gen.writeStartObject(); gen.writeFieldName("name"); gen.writeString(PERSON_SERIALIZER_ADDED_PREFIX + value.getName()); gen.writeEndObject(); } } private static class PersonDeserializer extends JsonDeserializer<Person> { @Override public Person deserialize(final JsonParser parser, final DeserializationContext ctx) throws IOException { final JsonNode rootNode = parser.getCodec().readTree(parser); final JsonNode nameNode = rootNode.get("name"); final String name; if (nameNode != null && nameNode.isTextual()) { name = PERSON_DESERIALIZER_ADDED_PREFIX + nameNode.asText(); } else { name = null; } return new Person(name); } } }
SSLContext
By default, the SSLContext implementation use this example SslExampleTest defined in ArangoDB Java Driver.
It is convenient for most SSL use case. It can use certificate embedded inside the application or outside.
If you want to provide your own implementation returning the SSLContext you can do it by implementing your own ArangodbSSLContextProvider
.
import javax.net.ssl.SSLContext;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;
import io.quarkiverse.arangodb.client.ext.runtime.ArangodbClientConfig;
import io.quarkiverse.arangodb.client.ext.runtime.ArangodbSSLContextException;
import io.quarkiverse.arangodb.client.ext.runtime.ArangodbSSLContextProvider;
@Singleton
public final class CustomArangodbSSLContextProviderProducer {
static final class CustomArangodbSSLContextProvider implements ArangodbSSLContextProvider {
@Override
public SSLContext provide() throws ArangodbSSLContextException {
throw new RuntimeException("TODO");
}
}
@Singleton
@Produces
public ArangodbSSLContextProvider customArangodbSSLContextProviderProducer(
final ArangodbClientConfig arangodbClientConfig) {
return new CustomArangodbSSLContextProvider();
}
}
Extension Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Type |
Default |
|
---|---|---|
communication protocol, possible values are: VST, HTTP_JSON, HTTP_VPACK, HTTP2_JSON, HTTP2_VPACK, (default: HTTP2_JSON) Environment variable: |
|
|
connection and request timeout (ms), (default 0, no timeout) Environment variable: |
int |
|
username for authentication, (default: root) Environment variable: |
string |
required |
password for authentication Environment variable: |
string |
required |
use SSL connection, (default: false) Environment variable: |
boolean |
|
enable hostname verification, (HTTP only, default: true) Environment variable: |
boolean |
|
VST chunk size in bytes, (default: 30000) Environment variable: |
int |
|
max number of connections per host, (default: 1 VST, 1 HTTP/2, 20 HTTP/1.1) Environment variable: |
int |
|
max lifetime of a connection (ms), (default: no ttl) Environment variable: |
long |
|
VST keep-alive interval (s), (default: no keep-alive probes will be sent) Environment variable: |
int |
|
acquire the list of available hosts, (default: false) Environment variable: |
boolean |
|
acquireHostList interval (ms), (default: 3_600_000, 1 hour) Environment variable: |
int |
|
load balancing strategy, possible values are: NONE, ROUND_ROBIN, ONE_RANDOM, (default: NONE) Environment variable: |
|
|
amount of samples kept for queue time metrics, (default: 10) Environment variable: |
int |
|
Type |
Default |
|
host hostname Environment variable: |
string |
required |
host port Environment variable: |
int |
required |
sslTruststore configuration This configuration section is optional |
Type |
Default |
location where to find the cert file Environment variable: |
path |
required |
trustStore password Environment variable: |
string |
required |