Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Settings nodes are the intermediate representation of configuration data and one of the core concepts of the library. Their main purpose is to abstract away various configuration formats (JSON, XML, etc.) so that binding to model classes can be implemented once without regard to the nature of configuration sources being used.
A settings node is a tree with string keys and string values in its leaf nodes.
Configuration sources produce settings nodes as their primary artifacts.
Binding is the process of converting a settings node to an arbitrary .NET object.
Settings nodes are implemented in the abstractions module.
Settings nodes are immutable objects.
Despite being a somewhat internal API used directly only in a handful of advanced scenarios, settings nodes are crucial for a solid understanding of how configuration data translates to objects in C# code via different node types and scoping.
Implementation of a custom configuration source;
Implementation of a custom model binder;
Transformation of configuration sources
There are three types of nodes:
Value nodes, used to hold data;
Array nodes, used to represent sequences;
Object nodes, used to represent objects with named properties.
Users are not expected to implement custom node types.
Null
node instances represent absence of settings (e.g. a configuration file that does not exist).
All node types implement the ISettingsNode interface and have following properties:
Property
Type
Description
Name
string
Node name. Case-insensitive.
Value
string
Children
IEnumerable<ISettingsNode>
this[name]
ISettingsNode
All nodes also implement a merge operation.
All node types implement a JSON-like ToString()
method. Its result may look like this for a sample object node with two nested value nodes:
This representation is extensively used on the rest of the pages.
Node value. Only present in .
Sequence of child nodes in containers: and .
Name-based indexer used to navigate (see ).
Vostok.Configuration is a set of libraries offering configuration tools to .NET applications. It handles fetching configuration data from files or APIs, parsing and converting it to user-defined model classes.
Configuration data sources should be composable regardless of storage formats.
It should be easy to combine settings from JSON files, environment variables and custom APIs. This is achieved with configuration source concept and universal settings nodes abstraction.
"Hot" configuration updates should be easy to leverage.
Hot configuration implies handling changes in settings without application restart. This is enabled by explicitly reactive source design and settings provider methods.
Rich object models should be supported for user convenience.
Binding process supports a wide range of primitives, collections, classes, interfaces and employs a number of conventions for arbitrary types (anything with a TryParse
method works).
It should be possible to extend the library with arbitrary data sources and object models.
Two major extensions points are custom configuration sources and binders.
On-demand and subscription-based methods to obtain settings;
Support for custom settings validation;
Wide selection of built-in sources;
Composable binders;
Sources can be combined, navigated and transformed;
Integration with Microsoft configuration system;
See obtain / observe settings scenarios, caching and error handling sections for details.
Array nodes are containers used to represent sequences, such as JSON arrays. Each array node contains an ordered list of child nodes. There is no limit to nesting: arrays can contain other arrays and objects. Elements of an array are not required to have names.
Array nodes are typically mapped to ordered collections during binding.
roperty
Description
Name
Value
Children
Returns an ordered sequence of child nodes.
ChildrenCount
Returns the number of elements in the Children
sequence.
this[name]
Two array nodes are considered equal if their Children
sequences are equal elementwise and their names match up to differences in case.
Object nodes are containers used to represent objects with named fields/properties. Each object node contains a map of child nodes with their names as keys. There is no limit to nesting: objects can contain other objects and arrays. Elements of an object are required to have non-null names.
Object nodes are typically mapped to arbitrary classes and structs during binding.
roperty
Description
Name
Value
Children
Returns an unordered sequence of child nodes.
ChildrenCount
Returns the number of elements in the Children
sequence.
this[name]
Returns a child node with given name or null
if such a node does not exist.
Two object nodes are considered equal if their Children
sequences are equivalent (contain equal elements but may present different order) and their names match up to differences in case.
Each node type implements a Merge
method that accept another node (right
in our terms) and an instance of merge options with following properties:
If both nodes are null
, the result is also null
.
If one of the nodes is null
, the non-null node wins:
left + null --> left
null + right --> right
If nodes are of different types, the right node always wins:
value + array --> array
array + object --> object
object + value --> value
If nodes are of same type, special rules apply. They are described in the next sections.
Right node always wins: left value + right value --> right value
.
Replace style (default) always preserves the right array:
left array + right array --> right array
.
Concat style produces an array containing elements from both arrays. All elements from the left array, then all elements from the second one, preserving order inside arrays.
[1, 2] + [2, 3] --> [1, 2, 2, 3]
Union style produces an array containing unique items from both arrays. The order is the same to Concat style.
[1, 2, 3] + [2, 3, 4] --> [1, 2, 3, 4]
Per element style produces an array containing items obtained by merging corresponding items (by index) from both arrays. If merged arrays have different children count, the "tail" of the longer array is preserved as-is.
[1, 2, 6] + [4, 5] --> [4, 5, 6]
Deep style (default) produces an object with union of the children from both nodes, then merges children with same names recursively.
{A:1} + {B:2} --> {A:1, B:2}
{A: {C:1}, B: {D:2}} + {A: {E:3}, B: {F:4}} --> {A: {C:1, E:3}, B: {D:2, F:4}}
Shallow style compares children of both nodes by names. If the sets of names match, regardless of order, merges the pairs of matching children recursively. Elsewise, just prefers to return the right node.
{A:1} + {B:2} --> {B:2}
{A:1} + {A:2} --> {A:2}
On each update, triggered either periodically or by an internal event, the source emits a pair: (settings, null)
on success or (null, error)
on failure. It's not required to deduplicate settings or errors at this level, although it's never wrong to do so.
Sources must never block indefinitely while waiting for data and should rather publish null
settings after a short initial timeout.
Sources must be thread-safe and should be designed to support multiple concurrent observers. It is also expected that every new observer would immediately receive a notification with current state upon subscription.
Here are some of the often used source implementations:
Required if nested in an , optional otherwise.
Always returns null
. Only can have values.
Always returns null
. Arrays cannot be navigated with .
Required if nested in an , optional otherwise.
Always returns null
. Only can have values.
Merge is the operation of reducing two to one: left + right --> result
.
Its primary use is to .
is a handy public helper used to merge arbitrary nodes that handles nulls:
Configuration sources fetch data from storage (local files or remote APIs) and convert it to , abstracting away actual data formats such as JSON or YAML.
They are not meant to be consumed directly and should be used in conjunction with a (see and scenarios).
Sources are also responsible for data change detection. They expose a with subscription support:
It's also possible to .
Binding is the process of initializing a model (an instance of almost arbitrary type) with data from a obtained from a :
The resulting model is queried by the application code with a to access settings.
Binding is implemented by a set of , each of which knows how to convert a to an object of a specific type.
Binders are composable: if there's a registered binder for Dictionary<T1, T2>
, string
and int
, then it's possible to bind to Dictionary<string, int>
. This is heavily used for .
A typical binding process starts with a and proceeds downward by matching fields and properties with node subtrees by names and invoking appropriate binders:
See all to learn more about this process.
Binding fails if there's at least one error on any level. Errors may arise from incorrect value formats for , missing values for fields and properties, mismatches of types or missing for requested types.
Property
Values
Description
ObjectMergeStyle
Deep
, Shallow
Type of the merge procedure performed on object nodes. Deep
is the default style.
ArrayMergeStyle
Replace
, Concat
, Union
, PerElement
Type of the merge procedure performed on array nodes. Replace
is the default style.
Here's the simplest way to experience Vostok.Configuration for the first time:
Install main, abstractions and sources.json modules:
Define a settings model class:
Create a JSON file with settings:
Obtain a model instance with a configuration provider from a file source and print it:
Great job! Here are some possible next steps:
Learn the most basic concepts: settings nodes, sources, binding, provider;
Explore available modules and source implementations;
Go over the basic scenarios section.
This library contains core interfaces (IConfigurationSource, IConfigurationProvider), intermediate data model (settings nodes) and attributes used to annotate user models, such as RequiredAttribute.
GitHub repository: vostok/configuration.abstractions
NuGet package: Vostok.Configuration.Abstractions
Cement users should reference this module with the following command:
This library contains an implementation of configuration provider, binding, and a couple of custom configuration primitives (such as DataSize and DataRate).
GitHub repository: vostok/configuration
NuGet package: Vostok.Configuration
Cement users should reference this module with the following command:
Configuration providers cache bound settings for each (type, source)
pair where sources are compared by reference. Caching ensures a solid performance level: only the first Get call is somewhat expensive while all the subsequent ones are extremely cheap. The cache is automatically updated when the underlying source issues new data.
Due to caching, configuration provider instances should be reused as much as possible. Ideally there should be just one singleton instance in the application.
Special care should be taken when using Get and Observe methods with short-lived source instances passed on per-call basis. This could cause poor performance due to cache misses and even lead to cache overflow events. Overflow events may cause violations of error handling guarantees. Default cache capacity is 50 but can be tuned in provider settings:
This pitfall is easy to fall into as all of the source-related extensions (combine, scope, transform, etc) return decorators that are treated as distinct sources. The only viable solution is to cache these derivative sources.
Value nodes are key-value pairs with optional keys. They cannot have child nodes and thus are always the leaves of settings node trees. Standalone values are rare: most of the time value nodes can be found inside objects or arrays.
Value nodes are typically mapped to primitive types during binding.
Property
Description
Name
Value
Useful payload. The value of a object field or array element. Can be null.
Children
Always returns an empty sequence.
this[name]
Always returns null
.
Two value nodes are considered equal if their values match exactly and their names match up to differences in case.
Scoping is the operation of navigating a settings node tree by accessing child nodes of objects via names in a case-insensitive manner. A sequence of names resembling a path in the object structure, such as ["property1", "property2"]
is called a scope.
Scoping does not work on value and array nodes (always results in null
).
Scoping is used to map object fields/properties to nodes of settings tree during binding. It also allows to scope sources — create a source that returns a subtree of settings returned by the original source.
{A: 1}
scoped to a
is just a value of 1
.
{A: 1}
scoped to b
is null
.
{A: {B: [1, 2]}}
scoped to A
is {B: [1, 2]}
{A: {B: [1, 2]}}
scoped to [A, B]
is [1, 2]
{A: {B: [1, 2]}}
scoped to [A, B, C]
is null
.
Required if nested in an , optional otherwise.
This library contains configuration implementations: and . It also exposes a configuration for integration with other sources, such as .
GitHub repository:
NuGet package:
users should reference this module with the following command:
This library contains a that fetches settings from . It also allows to apply custom parsers in order to support formats other than CC's native one.
GitHub repository:
NuGet package:
users should reference this module with the following command:
This library contains configuration implementations: and . It also exposes a configuration for integration with other sources, such as .
GitHub repository:
NuGet package:
users should reference this module with the following command:
This library contains a few basic implementations of , such as , , , , . It also hosts a set of extensions to manipulate sources (, , , , , ) and helpers aiding in .
GitHub repository:
NuGet package:
users should reference this module with the following command:
This library contains configuration implementations: and . It also exposes a configuration for integration with other sources, such as .
GitHub repository:
NuGet package:
users should reference this module with the following command:
is responsible for the and offers methods to obtain final settings models. It's also responsible for and . Providers are used directly by the application code to either or .
The one without any parameters requires a prior of a source to the requested type;
The one with a parameter requires no such assignment (check out for gotchas);
This library provides a one-way integration with Microsoft configuration extensions, allowing to inject Vostok sources as MS configuration providers.
GitHub repository: vostok/configuration.microsoft
NuGet package: Vostok.Configuration.Microsoft
Cement users should reference this module with the following command:
This source is not documented yet. Come back for updates later.
Location: main sources module.
ObjectSource converts an arbitrary object (including anonymous types) to a settings node:
Conversion process obeys the following priority list:
Objects with overridden ToString
are converted to value nodes;
Dictionaries with primitive key types are converted to object nodes;
Conversion is performed recursively for keys and values;
Objects that implement IEnumerable
are converted to array nodes;
Conversion is performed recursively for sequence elements;
Everything else is converted to object nodes;
Conversion is performed recursively for public instance fields and properties;
Like constant sources, object source does not produce any updates by itself; conversion exceptions are exposed via (null, error)
notifications.
This library provides an integration with Vostok logging, allowing to log errors and settings updates.
GitHub repository: vostok/configuration.logging
NuGet package: Vostok.Configuration.Logging
Cement users should reference this module with the following command:
Location: main sources module.
A constant source returns a preconfigured settings node and never issues any updates.
A lazy constant source does the same but defers the node acquisition until first subscription:
Errors in the provided delegate are propagated via (null, error)
notification. It's guaranteed to execute not more than once.
Constant sources are handy for unit testing and may serve as base classes for custom sources.
This library contains a source implementation based on HashiCorp Vault secrets.
GitHub repository: vostok/configuration.sources.vault
NuGet package: Vostok.Configuration.Sources.Vault
Cement users should reference this module with the following command:
Location: Sources.Xml module.
XmlStringSource parses well-formed XML documents from in-memory strings and supports manual external updates:
XmlFileSource parses XML files and automatically watches for changes:
A file that does not exist simply leads to a null
node, which implies default settings values unless something is explicitly required.
Location: main sources module.
EnvironmentVariablesSource converts process environment variables into settings nodes.
Keys with dots, colons and double underscores are treated as hierarchical and get split into segments:
This source is static and never issues data updates.
It can be summarized in two simple rules:
If an error occurs and no correct settings instance has been observed for the requested type thus far, the error is propagated to the calling code, resulting in exceptions from Get method. A subsequent settings update with correct data automatically "heals" future Get calls.
This page describes how deal with errors arising from and .
If an error occurs and a correct settings instance has already been observed for the requested type at least once, the error is and does not cause Get method to fail: last seen correct instance is returned from cache instead.
The second guarantee can be violated by cache overflow events. Read the to find out how to avoid them.
Observe method never produces OnError
or OnCompleted
notifications. It only reports successful settings updates. The errors are handled in background and .
This source is not documented yet. Come back for updates later.
Binders convert settings nodes to objects. There are built-in binders for primitives, collections, classes and structs. One can also extend the library by implementing a custom binder.
Location: Sources.Yaml module.
YamlStringSource parses well-formed YAML documents from in-memory strings and supports manual external updates:
YamlFileSource parses YAML files and automatically watches for changes:
A file that does not exist simply leads to a null
node, which implies default settings values unless something is explicitly required.
Arrays and lists:
T[]
List<T>
IEnumerable<T>
(backed by T[]
)
IReadOnlyList<T>
(backed by T[]
)
IReadOnlyCollection<T>
(backed by T[]
)
ICollection<T>
(backed by List<T>
)
IList<T>
(backed by List<T>
)
Dictionaries:
Dictionary<TKey, TValue>
IDictionary<TKey, TValue>
(backed by Dictionary
)
IReadOnlyDictionary<TKey, TValue>
(backed by Dictionary
)
Sets:
HashSet<T>
ISet<T>
(backed by HashSet
)
Collections can have classes, structs and other collections as elements.
Collections require binders defined for element types (both keys and values in case of dictionaries).
Array or object node. Empty nodes are converted to empty collections.
An empty collection is returned unless explictly required.
Nulls are not valid as dictionary keys.
Any element binding failure results in complete collection binding failure.
A custom element comparer (such as StringComparer.OrdinalIgnoreCase
for case-insensitive dictionary keys) can be achieved by wrapping the collection in a custom type and utilizing the constructor binding convention.
The binder starts off by creating an instance of the bound type with default values of public fields and properties (the same way as Activator.CreateInstance
does) and then recursively binds the value of every field and property to a corresponding subsection of the settings tree. See binding nodes to models page for a step-by-step illustration of this process.
Fields and properties don't have to be mutable: readonly fields and properties with private setters (or even without one) are fine.
Following members are ignored: indexers, constants, static and non-public fields and properties, computed properties without backing fields.
Nested classes and collections are supported without restrictions.
When binding a field or property value, the settings tree is scoped to the member name. This imposes a requirement to synchronize naming in configuration data provided by sources and models in the application code. In order to bind a model property named Timeout
from a section of JSON file, this section must contain a property with the same name (barring case).
Name aliases allow to decouple property names from configuration data names.
By default all fields and properties are treated as optional: they are just left with default values if no data could be found in the settings tree (default values may come from field and property initializers). It's possible to make members required though. Required members with no data in the settings tree cause the binding process to fail and produce an exception.
Model classes must either have a parameterless constructor, a constructor with a single parameter, or have an OmitConstructorsAttribute
(which allows creating an uninitialized object, therefore, no default parameters are present).
Binders are required for all public fields and properties.
Only object nodes are supported.
Location: Sources.Json module.
JsonStringSource parses well-formed JSON documents from in-memory strings and supports manual external updates:
​JsonFileSource parses JSON files and automatically watches for changes:
A file that does not exist simply leads to a null
node, which implies default settings values unless something is explicitly required.
Primitive types are parsed from string values in value nodes.
Type
Allowed format examples
string
Literally any string.
char
Literally any single character.
bool
true
, True
, TRUE
byte, sbyte
Anything built-in TryParse
method can grok.
short, ushort
Anything built-in TryParse
method can grok.
int, uint
Anything built-in TryParse
method can grok.
long, ulong
Anything built-in TryParse
method can grok.
float, double, decimal
1.23
, 1,23
, 5,12e2
Guid
Anything built-in TryParse
method can grok.
Uri
http://example.com
, example.com/some
, /part/of/path
TimeSpan
00:12:34
, 2 seconds
, 500 ms
, 1.5 days
, 10s
, 0.5 minutes
DateTime(Offset)
2018-03-14 15:09:26.535
, 20050809T181142+0330
IPAddress
127.0.0.1
, 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d
IPEndPoint
192.168.1.10:80
453453
(just bytes), 1 kb
, 24.3 megabytes
, 500 TB
500
(just bytes/sec), 200 kilobytes/second
, 5 GB/sec
, 80 mb/s
Encoding
utf-8
, us-ascii
Enum
types
Anything built-in TryParse
method can grok.
Nullable structs
A valid value or null
.
DataSize and DataRate are custom new types. They also provide factory extensions and operators:
There's also support for types that implement Parse
or TryParse
method with standard signature:
This allows to use arbitrary types with string representation without resorting to implementation of custom binders.
Value node or a container (array/object) node with a single value node child.
Default value for the type is used unless explictly required.
Explicitly specified null
string value has the same effect.
Parsing errors arising from incorrect value formats lead to binder failure and result in exceptions, even for optional members (see classes and structs for more context).
A source can only be assigned to a type before any Get or Observe calls are made for that type:
It supports 7 syntax options for key-value parameters:
--key=value
--key value
-key=value
-key value
/key=value
/key value
key=value
Keys with dots (such as a.b.c
) are treated as hierarchical and get split into segments:
Multiple occurrences of the same key are merged into arrays.
This source is static and never issues data updates.
Standalone keys may optionally be supplied with a default value.
Standalone values may optionally be grouped under default key.
Settings models can be printed in JSON or YAML formats to generate configuration file examples.
Requires: .
When using , Get and Observe method overloads without parameters require an explicit association of the model type with a .
A type that has exactly one constructor with a single argument of a type that can be bound can also be bound with argument injection. This is especially useful for customization:
Location: .
converts CLI arguments into settings nodes.
Requires: .
See for the log interface required here.
Secret settings should be to avoid being logged.
Requires: .
Requires: logging module.
See error handling for cases where this logging is useful.
See logging documentation for the log interface required here.
Secret settings should be marked with an attribute to avoid being logged.
Requires: main module.
Settings can be obtained on-demand with configuration provider's Get method.
With prior assignment of sources:
With sources passed on per-call basis:
Always returns the most recent version of settings (updates may happen in background):
Call Get on every settings access for "hot" configuration;
Call Get once and cache the result for "cold" configuration;
First call for a type may block or throw exceptions due to source latency, data unavailability or incorrect data format. This behavior persists until a data update remedies the error;
Once warmed up, subsequent calls never block or throw errors and are extremely cheap due to caching. Future errors are not propagated to the calling code, but can be logged. Get calls return the last seen correct settings object;
Requires: abstractions module.
By default settings are subject to logging, which is generally not appropriate for secrets. This can be fixed either by disabling logging entirely on the configuration provider or by annotating fields and properties of the model with [Secret]
attribute:
Requires: abstractions module.
By default all settings are optional; that is, absence of relevant data in the source results in default values during binding and does not produce errors.
Some parts of configuration may be crucial to the application to the point that it's pointless to start without them being initialized. These fields and properties should be annotated with [Required]
attribute:
Default behavior can be inverted in the scope of a type with [RequiredByDefault]
attribute. Individual fields and properties can then be made optional with [Optional]
attribute:
Requires: main module.
One can subscribe to settings updates for a type with configuration provider's Observe method.
With prior assignment of sources:
With sources passed on per-call basis:
Temporary subscriptions should be disposed of as they might hold on to resources in sources:
OnError
and OnCompleted
notifications are never produced. Only successful settings updates are propagated to the observers. This means that there will be no notifications if initial attempt to provide settings fails with an error and no further data updates are published by the source.
The only way to notice errors when using Observe is to enable error logging.
The subscription is not guaranteed to immediately produce a notification. The first notification may be delayed due to data not having been fetched from the source yet. However, once a valid settings instance has been observed, all new observers receive a notification with current actual settings upon subscription. This notification is published on a background thread, so don't count on it being delivered after Subscribe
call completes.
It's recommended to obtain initial settings instance with Get method before subscribing to updates with Observe. This practice ensures correct error propagation, warms up the cache and eliminates situations where the settings are not ready yet on access attempt.
Requires: main sources module.
Multiple configuration sources can be combined into a single composite source whose data is produced by merging the settings trees provided by original sources.
Order of the sources is important: settings from sources that come later in the list have greater priority, hence the rightmost source should be the most specific/significant. In other words, merging is performed in a left-to-right fashion.
Updates are pushed to subscribers each time one of the component sources generates new settings.
Requires: main module.
The basic recommended way to get most up-to-date settings in the presence of background updates is to use provider's Get method (see the relevant scenario) on each access attempt. However, it's also possible to obtain an inherently dynamic settings object whose properties are updated under the hood. This requires to use an interface as the settings model:
Note that in order to get a guaranteed consistent view of the settings without "tearing" (observing a mix of values from before and after update due to a race condition) when using a nested object (section), it's recommended to access a snapshot of this nested object saved in a variable:
Requires: main sources module.
A source can be scoped just like settings nodes can. Resulting source's data is exactly base source's data scoped to given path:
It's intended use case is self-sufficient configuration in libraries: library authors may not want to force their users to provide an IConfigurationProvider
instance each time they're using library classes. Instead, these classes could just obtain a log from the shared property:
By default this property returns a singleton provider with default settings. It can be configured explicitly in the application:
Requires: .
This operation allows to modify source contents by applying a custom delegate. Most often this involves rewriting values stored in :
Requires: .
class has a property named Default
that serves as a global static source of IConfigurationProvider
instance.
Requires: .
Requires: abstractions module.
Name aliases provide alternative keys to look for in settings nodes when performing binding.
They can be applied to fields and properties with a special attribute:
It's allowed to assign multiple aliases to a single member:
Aliases do not handle ambiguity. If the source returns data with more than one of the lookup keys assigned to a field or property, binding fails with an error.
There are also built-in validation constraints you can use to create a custom validator. Just inherit your validator class from ConstraintsValidator
and override a method returning constraints to be checked:
Here's a list of all currently implemented constraint types:
NotNullConstraint
for arbitrary reference types;
NotNullOrEmptyConstraint
for strings;
NotNullOrWhitespaceConstraint
for strings;
RangeConstraint
, LessConstraint
, LessOrEqualConstraint
, GreaterConstraint
and GreaterOrEqualConstraint
for any types that implement IComparable
;
UniqueConstraint
to check that a set of field/properties only contains unique values;
Requires: , (constraints).
Validation feature allows to associate a custom user-made validator with a settings type. Validation occurs during and results in binding errors for incorrect settings.
Requires: .
This extension point covers scenarios where are not sufficient to handle required types.
Custom binders should be used as a last resort when and are not powerful enough.
Requires: , .
Requires: .
Value substitution feature allows to replace placeholders in settings data. It's based on the and uses #{}
syntax for placeholders.
Requires: .
Requires: sources module.
Sources module offers a couple of base classes to assist in implementing custom configuration sources.
For in-memory sources or sources that can update an in-memory one from a remote API.
For sources based on local files. Includes automatic reload on file updates.
Requires: .