Provisions
A Provision
is a wrapper of a singleton object that can be constructed by using a java.util.Properties
instance.
Provisions
solve several problems that arise in configuration-oriented Nstream codebases, including:
- Instantiation of resources used by a Swim server, not just web agents
- Config deduplication when multiple web agents need to utilize the same or a similar resource
- Environment dependent evaluations of resource definitions.
Overview
Upon initial startup (via nstream.adapter.runtime.AppPlane#main
) an Nstream server loads all declared Provisions
under the top-level provisions
block in server.recon
.
If you wish to use a custom main
method but still keep this behavior, just invoke the following logic
import nstream.adapter.runtime.ToolkitLoader;
// ...
// Load provisions and return a handle to server config structure
final Value serverStructure = ToolkitLoader.loadConfig();
// Run server
ToolkitLoader.runKernel();
Once a Provision<T>
is successfully declared under name
, then its value can be read at any time via ProvisionLoader<T>.get(name).value()
.
Avoid namespace collisions
Loading multiple provisions with the same name will result in undefined behavior, and not necessarily throw a reliable exception.
Note: the IngressSettings and EgressSettings definitions from some adapter libraries contain fields that refer to provision names.
The corresponding patch implementations read these fields and invoke the aformentioned get()
method.
This is how Nstream provides config-only pathways for common-case situations even when independently-loaded singletons are required.
Declaration Styles
Inline
The following server.recon
snippet declares a ConnectionPoolProvision
under the name hikari
, and a separate PropertiesProvision
under the name consumer-props
.
provisions: {
@provision("hikari") {
# class must extend
# nstream.adapter.common.provision.AbstractProvision
class: "nstream.adapter.jdbc.ConnectionPoolProvision"
def: {
"dataSource.driver": "oracle.jdbc.OracleDriver"
}
},
@provision("consumer-props") {
class: "nstream.adapter.common.provision.PropertiesProvision",
def: {
"bootstrap.servers": "broker:9092",
"group.id": "fooGroup",
"auto.offset.reset": "latest"
}
}
}
External Config File
Use use
in place of def
to load from a configuration file (falling back to a Java resource).
# /path/to/consumer.properties
bootstrap.servers=broker:9092
group.id=fooGroup
auto.offset.reset=latest
# server.recon
provisions: {
@provision("consumer-props") {
class: "nstream.adapter.common.provision.PropertiesProvision",
use: "/path/to/consumer.properties"
}
}
- A
Record
containing multiple Strings can also be placed in ause
block, in which case the union of the properties within the files are used, and earlier-declared files receive higher priority if duplicates are encountered. - If both a
use
and adef
are provided, then properties in theuse
block take precedence, i.e. the onlydef
properties that take any effect are those without a counterpart inuse
.
Environment-Sensitive Inline
The syntax @config { env: ..., prop: ..., def: ...}
can replace any inline property.
This indicates to evaluate a specified environment variable, or fall back to a specied system property, or fall back to a specified default definition.
The env
/prop
/def
priority order is enforced regardless of the order of declaration.
provisions: {
@provision("consumer-props") {
class: "nstream.adapter.common.provision.PropertiesProvision",
def: {
"bootstrap.servers": @config {
env: "BOOTSTRAP_SERVERS"
prop: "bootstrap.servers"
def: "bootstrap-server:9092"
},
"group.id": "fooGroup"
}
}
}
Note: all three of env/prop/def are technically optional, though a RuntimeException
will occur if evaluating the expression results in no defined value.
Environment-Sensitive External Config File
The syntaxes discussed in the previous two sections can be combined.
provisions: {
@provision(complex) {
class: "nstream.adapter.common.provision.PropertiesProvision"
use: {
"loadconf/base.properties",
@config {
env: "SECRET_PROPERTIES_FILE"
prop: "secret.properties.file"
def: "loadconf/unused.properties"
}
}
def: {
"value.deserializer": "org.apache.kafka.common.serialization.IntegerDeserializer"
# This is ignored if loadconf/base.properties OR the @config evaluation result is
# a file that includes a bootstrap.servers property; otherwise, it is evaluated
# normally and appended to the definition.
"bootstrap.servers": @config {
env: "BOOTSTRAP_SERVERS"
prop: "bootstrap.servers"
def: "bootstrap-server:9092"
}
}
}
}
Usage Tips
- If a
Provision
is used by a single web agent, or if multiple web agents can use them concurrently without breaking APIs or logical correctness, then the advice in this article can be followed directly. - If many
Provisions
of a type must be instantiated and they all follow identical configurations, you may be able to minimize redundant configurations by creating aPropertiesProvision
containing the definition as a proxy object, and then instantiating as many objects as you need in code.
Nstream is licensed under the Redis Source Available License 2.0 (RSALv2).