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
Recordcontaining multiple Strings can also be placed in auseblock, 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
useand adefare provided, then properties in theuseblock take precedence, i.e. the onlydefproperties 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
Provisionis 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
Provisionsof a type must be instantiated and they all follow identical configurations, you may be able to minimize redundant configurations by creating aPropertiesProvisioncontaining 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).