Vert.x Config provides a way to configure your Vert.x application. It:
offers multiple configuration syntaxes (JSON, properties, Yaml (extension), Hocon (extension)…
offers multiple configuration stores such as files, directories, HTTP, git (extension), Redis (extension), system properties and environment properties.
lets you define the processing order and overloading
supports runtime reconfiguration
The library is structured around:
a*Config Retriever instantiated and used by the Vert.x application. It configures a set of configuration store Configuration store** defines a location from where the configuration data is read and also a format (JSON by default)
The configuration is retrieved as a JSON Object.
To use the Config Retriever, add the following dependency to the dependencies section of your build descriptor:
Maven (in your pom.xml
):
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config</artifactId>
<version>3.6.2</version>
</dependency>
Gradle (in your build.gradle
file):
compile 'io.vertx:vertx-config:3.6.2'
----
Once done, you first need to instantiate the ConfigRetriever
:
def retriever = ConfigRetriever.create(vertx)
By default, the Config Retriever is configured with the following stores (in this order):
The Vert.x verticle config()
The system properties
The environment variables
A conf/config.json
file. This path can be overridden using the vertx-config-path
system property or
VERTX_CONFIG_PATH
environment variable.
You can configure your own stores:
def httpStore = [
type:"http",
config:[
host:"localhost",
port:8080,
path:"/conf"
]
]
def fileStore = [
type:"file",
config:[
path:"my-config.json"
]
]
def sysPropsStore = [
type:"sys"
]
def options = [
stores:[
httpStore,
fileStore,
sysPropsStore
]
]
def retriever = ConfigRetriever.create(vertx, options)
More details about the overloading rules and available stores are available below. Each store can be marked as
optional
. If a failure is caught while retrieving (or processing) the configuration from an optional store, the failure
is logged, but the processing does not fail. Instead, an empty JSON object is returned ({}
). To mark a store as
optional, use the optional
attribute:
def fileStore = [
type:"file",
optional:true,
config:[
path:"my-config.json"
]
]
def sysPropsStore = [
type:"sys"
]
def options = [
stores:[
fileStore,
sysPropsStore
]
]
def retriever = ConfigRetriever.create(vertx, options)
Once you have the instance of the Config Retriever, retrieve the configuration as follows:
retriever.getConfig({ ar ->
if (ar.failed()) {
// Failed to retrieve the configuration
} else {
def config = ar.result()
}
})
The declaration order of the configuration store is important as it defines the overloading. For conflicting key, configuration stores arriving last overloads the value provided by the previous configuration stores. Let’s take an example. We have 2 configuration stores:
A
provides {a:value, b:1}
B
provides {a:value2, c:2}
Declared in this order (A, B), the resulting configuration would be:
{a:value2, b:1, c:2}
.
If you declare them in the reverse order (B, A), you will get: {a:value, b:1, c:2}
.
The retrieve configuration allows:
configuring verticles,
configure ports, clients, locations and so on,
configuring Vert.x itself
This section gives a few examples of usage.
The following example can be placed in the start
method of a verticle. It retrieves the configuration (using the
default stores), and configure an HTTP server with the content of the configuration.
def retriever = ConfigRetriever.create(vertx)
retriever.getConfig({ json ->
def result = json.result()
vertx.createHttpServer().requestHandler({ req ->
result.message
}).listen(result.port)
})
The following example configures 2 verticles using the configurations contained in the verticles.json
file:
def retriever = ConfigRetriever.create(vertx, [
stores:[
[
type:"file",
config:[
path:"verticles.json"
]
]
]
])
retriever.getConfig({ json ->
def a = json.result().a
def b = json.result().b
vertx.deployVerticle(examples.GreetingVerticle.class.getName(), [
config:a
])
vertx.deployVerticle(examples.GreetingVerticle.class.getName(), [
config:b
])
})
You can also configure Vert.x directly. For this, you need a temporary Vert.x instance used to retrieve the configuration. Then the actual instance is created:
// Create a first instance of Vert.x
def vertx = Vertx.vertx()
// Create the config retriever
def retriever = ConfigRetriever.create(vertx, [
stores:[
[
type:"file",
config:[
path:"vertx.json"
]
]
]
])
// Retrieve the configuration
retriever.getConfig({ json ->
def result = json.result()
// Close the vert.x instance, we don't need it anymore.
vertx.close()
// Create a new Vert.x instance using the retrieve configuration
def options = result
def newVertx = Vertx.vertx(options)
// Deploy your verticle
newVertx.deployVerticle(examples.GreetingVerticle.class.getName(), [
config:result.a
])
})
Vert.x Config notifies you when the configuration changes. If you want to react to this event, you need to implement the reaction yourself. For instance, you can un-deploy and redeploy verticle or send the new configuration on the event bus. The following example shows this latter case. It sends the new configuration on the event bus. Interested verticles can listen for this address and update themselves:
def retriever = ConfigRetriever.create(vertx, [
stores:[
[
type:"file",
config:[
path:"verticles.json"
]
]
]
])
retriever.getConfig({ json ->
//...
})
retriever.listen({ change ->
def json = change.newConfiguration
vertx.eventBus().publish("new-configuration", json)
})
The Config Retriever provides a set of configuration stores and formats. More are available as extensions, and you can also implement your own.
Each declared data store must specify the type
. It can also define the format
. If
not set JSON is used.
Some configurations tore requires additional configuration (such a path…). This
configuration is passed as a Json Object using setConfig
This configuration store just read the configuration from a file. It supports all supported formats.
def file = [
type:"file",
format:"properties",
config:[
path:"path-to-file.properties"
]
]
The path
configuration is required.
The JSON configuration store serves the given JSON config as it is.
def json = [
type:"json",
config:[
key:"value"
]
]
The only supported format for this configuration store is JSON.
This configuration store transforms environment variables to a JSON Object contributed to the global configuration.
def json = [
type:"env"
]
This configuration store does not support the format
configuration. By default, the retrieved value is
transformed into JSON compatible structures (number, string, boolean, JSON object and JSON array). To avoid this
conversion, configure the raw-data
attribute:
def json = [
type:"env",
config:[
'raw-data':true
]
]
You can configure the raw-data
attribute (false
by default). If raw-data
is true
no attempts to convert
values is made, and you’ll be able to get raw values using config.getString(key)
. It is useful when manipulating
large integers.
If you want to select the set of keys to import, use the keys
attributes. It filters out all non selected keys. Keys
must be listed individually:
def json = [
type:"env",
config:[
keys:[
"SERVICE1_HOST",
"SERVICE2_HOST"
]
]
]
This configuration store transforms system properties to a JSON Object contributed to the global configuration.
def json = [
type:"sys",
config:[
cache:"false"
]
]
This configuration store does not support the format
configuration.
You can configure the cache
attribute (true
by default) let you decide whether or
not it caches the system properties on the first access and does not reload them.
You can also configure the raw-data
attribute (false
by default). If raw-data
is true
no attempts to convert
values is made, and you’ll be able to get raw values using config.getString(key)
. It is useful when manipulating
large integers.
This configuration store retrieves the configuration from an HTTP location. It can use any supported format.
def http = [
type:"http",
config:[
host:"localhost",
port:8080,
path:"/A"
]
]
It creates a Vert.x HTTP Client with the store configuration (see next snippet). To
ease the configuration; you can also configure the host
, port
and path
with the
host
, port
and path
properties.
def http = [
type:"http",
config:[
defaultHost:"localhost",
defaultPort:8080,
ssl:true,
path:"/A"
]
]
This event bus configuration store receives the configuration from the event bus. This stores let you distribute your configuration among your local and distributed components.
def eb = [
type:"event-bus",
config:[
address:"address-getting-the-conf"
]
]
This configuration store supports any format.
This configuration store is similar to the file
configuration store, but instead of
reading a single file, read several files from a directory.
This configuration store configuration requires:
a path
- the root directory in which files are located
at least one fileset
- an object to select the files
for properties file, you can indicate if you want to disable the type conversion using the raw-data
attribute
Each fileset
contains:
* a pattern
: a Ant-style pattern to select files. The pattern is applied on the
relative path of the files from the current working directory.
* an optional format
indicating the format of the files (each fileset can use a
different format, BUT files in a fileset must share the same format).
def dir = [
type:"directory",
config:[
path:"config",
filesets:[
[
pattern:"dir/*json"
],
[
pattern:"dir/*.properties",
format:"properties"
]
]
]
]
def dirWithRawData = [
type:"directory",
config:[
path:"config",
filesets:[
[
pattern:"dir/*json"
],
[
pattern:"dir/*.properties",
format:"properties",
'raw-data':true
]
]
]
]
Vert.x Config can read properties file. When reading such a file, you can pass the raw-data
attribute to
indicate to Vert.x to not attempt to convert values. It is useful when manipulating large integers. Values can be
retrieved using config.getString(key)
.
def propertyWithRawData = [
format:"properties",
type:"file",
config:[
path:"raw.properties",
'raw-data':true
]
]
Some properties configuration maybe is hierarchical in nature.
When reading such a file, you can pass the hierarchical
attribute to
indicate to Vert.x to convert the configuration to a json object while maintaining this hierarchy,
in contrast to the previous method with a flat structure.
Example:
server.host=localhost server.port=8080 multiple.values=1,2,3
Get values:
def propertyWitHierarchical = [
format:"properties",
type:"file",
config:[
path:"hierarchical.properties",
hierarchical:true
]
]
def options = [
stores:[
propertyWitHierarchical
]
]
def configRetriever = ConfigRetriever.create(Vertx.vertx(), options)
configRetriever.configStream().handler({ config ->
def host = config.server.host
def port = config.server.port
def multiple = config.multiple.values
(0..<multiple.size()).each { i ->
def value = multiple[i]
}
})
The Configuration Retriever periodically retrieve the configuration, and if the outcome is different from the current one, your application can be reconfigured. By default, the configuration is reloaded every 5 seconds.
def options = [
scanPeriod:2000,
stores:[
store1,
store2
]
]
def retriever = ConfigRetriever.create(Vertx.vertx(), options)
retriever.getConfig({ json ->
// Initial retrieval of the configuration
})
retriever.listen({ change ->
// Previous configuration
def previous = change.previousConfiguration
// New configuration
def conf = change.newConfiguration
})
You can retrieve the last retrieved configuration without "waiting" to be retrieved using:
def last = retriever.getCachedConfig()
The ConfigRetriever
provide a way to access the stream of configuration.
It’s a ReadStream
of JsonObject
. By registering the right
set of handlers you are notified:
when a new configuration is retrieved
when an error occur while retrieving a configuration
when the configuration retriever is closed (the
endHandler
is called).
def options = [
scanPeriod:2000,
stores:[
store1,
store2
]
]
def retriever = ConfigRetriever.create(Vertx.vertx(), options)
retriever.configStream().endHandler({ v ->
// retriever closed
}).exceptionHandler({ t ->
// an error has been caught while retrieving the configuration
}).handler({ conf ->
// the configuration
})
You can configure a processor that can validate and update the configuration. This is done using the
setConfigurationProcessor
method.
The prcessing must not return null
. It takes the retrieved configuration and returns the processed one. If the processor
does not update the configuration, it must return the input configuration. If the processor can throw an exception (for
example for validation purpose).
The ConfigRetriever
provide a way to retrieve the configuration as a
Future
:
def future = ConfigRetriever.getConfigAsFuture(retriever)
future.setHandler({ ar ->
if (ar.failed()) {
// Failed to retrieve the configuration
} else {
def config = ar.result()
}
})
You can extend the configuration by implementing:
the ConfigProcessor
SPI to add support for a
format
the ConfigStoreFactory
SPI to add support for
configuration store (place from where the configuration data is retrieved)
Besides of the out of the box format supported by this library, Vert.x Config provides additional formats you can use in your application.
Unresolved directive in index.adoc - include::hocon-format.adoc[]
Unresolved directive in index.adoc - include::yaml-format.adoc[]
Besides of the out of the box stores supported by this library, Vert.x Config provides additional stores you can use in your application.
Unresolved directive in index.adoc - include::git-store.adoc[]
Unresolved directive in index.adoc - include::kubernetes-store.adoc[]
Unresolved directive in index.adoc - include::redis-store.adoc[]
Unresolved directive in index.adoc - include::zookeeper-store.adoc[]
Unresolved directive in index.adoc - include::consul-store.adoc[]
Unresolved directive in index.adoc - include::spring-store.adoc[]
Unresolved directive in index.adoc - include::vault-store.adoc[]