Skip to content

API for using mapped configurations in a standalone way #1001

@dmlloyd

Description

@dmlloyd

Mapped configurations are very good in terms of brevity, clean API, and usability. However, they are not usable unless you're using SmallRye Config to read a configuration and create them indirectly in that way.

As a configuration user, I would like to have the ability to use mapped configurations to configure my application using my application's API, but have the usage of the actual configuration file be optional. This way I can configure my application using a file, or programmatically, all using the same API.

To allow this, we should have an API which allows configuration interfaces to be instantiated directly using a builder API. The API could look something like this:

@ConfigMapping(prefix = "hello-world")
public interface HelloWorldConfig {
    @WithDefault("hello")
    String helloMessage();

    @WithDefault("5")
    int numberOfTimes();

    Optional<Path> outputPath();
}
    public static void main(String[] args) {
        ConfigMappingBuilderFactory factory = ...;
        // manually create a config
        ConfigMappingBuilder<HelloWorldConfig> configBuilder = factory.builder(HelloWorldConfig.class);
        configBuilder.set(HelloWorldConfig::helloMessage, "good morning!");
        // convenience for optional properties
        configBuilder.setOptional(HelloWorldConfig::outputPath, Path.of("/tmp/good-morning.txt"));
        // primitives without boxing
        configBuilder.set(HelloWorldConfig::numberOfTimes, 10);
        // oops, I made a mistake, let's revert to default
        configBuilder.unset(HelloWorldConfig::numberOfTimes);
        HelloWorldConfig config = configBuilder.build();
        // now do something with config
    }

An API with this style is easier to use than source code generation based approaches. The builder factory would track generated classes for each configuration interface, which ideally could be reused by the existing config mapping implementation (though that implementation would not use the builder API itself per se). When running on Java 16 and later, these implementation classes should likely be implemented as records.

The generated record would be able to enforce constraints like non-nullity and validation in its canonical constructor (ideally using smallrye-common-constraint to validate nullity and smallrye-config-validator for the user validation rules as expected), ensuring that the consuming code will always receive a correct object. Optional properties would default to Optional.empty() and properties with explicit defaults would have those defaults set if no value is given.

Note that by waiting for the baseline JDK to move from 11 to 17, in addition to using records on the back end, we could possibly use my backport of the JDK classfile API to make class generation easiest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions