Description
Quarkus configs to cater for multi nested properties
Implementation Idea
Example of how the application.properties/application.yaml file can be set up with nested properties:
eg application.properties:
application.environments[0].name=dev
application.environments[0].service[0].name=service1
application.environments[0].service[1].name=service2
application.environments[0].service[2].name=service3
eg application.yaml:
application:
environments:
- name: dev
services:
- name: service1
- name: service2
- name: service3
We then can have a Java Config Class as follows:
@ConfigProperties(prefix = "application")
public class AppConfiguration {
private List<Environment> environments;
...
public static class Environment {
private String name;
private List<Service> services
...
public static class Service {
private String name;
...
}
}
}
Hi @ismailsalloo,
The YAML ConfigSource already behaves that way. Check here: https://github.com/smallrye/smallrye-config/blob/6665fbd406d0d2f46c852df9a506b1ec3c2198c2/sources/yaml/src/test/java/io/smallrye/config/source/yaml/YamlConfigSourceTest.java.
Let me know if that works for you.
Hi @ismailsalloo,
The YAML ConfigSource already behaves that way. Check here: https://github.com/smallrye/smallrye-config/blob/6665fbd406d0d2f46c852df9a506b1ec3c2198c2/sources/yaml/src/test/java/io/smallrye/config/source/yaml/YamlConfigSourceTest.java.
Let me know if that works for you.
Hi @radcortez, that works well if you only have Strings in your list. The moment you start nesting the list, that's when Quarkus requires a Converter which is unable to cater for this scenario. It's not YAML configuration specifically, but we picked this up using YAML configuration and when we converted it to normal application.properties, we were unable to map the configs to Java using @ConfigProperties
Hi, @radcortez This feature is supported by the latest SmallRye Config, supporting Map,?> config properties (i.e. grouping). SmallRye Config Documentation.
The following mapping works when using SmallRye Config independently of Quarkus. It also supports interface based grouping of properties.
I've tried to make it work with Quarkus, even using CDI to create a bean of the configuration:
@Produces
public Configuration configuration() {
return new SmallRyeConfigBuilder()
.withMapping(Configuration.class)
.addDefaultSources()
.addDefaultInterceptors()
.build()
.getConfigMapping(Configuration.class);
}
public interface Configuration {
Boolean enabled();
Map<String, Endpoint> endpoint();
interface Endpoint {
Boolean enabled();
String path();
}
}
`
this method works correctly outside of a Quarkus context, but in quarkus I get the following error:
Caused by: java.lang.ClassNotFoundException: uk.singular.sportsbook.ConfigurationProvider$Configuration
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:412)`
Tried everything from registering for reflection to using Jandex to register the class. SmallRye Config does not support this type of mapping for actual classes, only interfaces.
With the current implementation, only using
io.quarkus.arc.config.ConfigProperties
and
org.eclipse.microprofile.config.inject.ConfigProperty
(for Quarkus to manage my injectable properties) the following exception occurs:
Caused by: javax.enterprise.inject.spi.DeploymentException: No config value of type [java.util.Map] exists for: test.endpoint
at io.quarkus.arc.runtime.ConfigRecorder.validateConfigProperties(ConfigRecorder.java:39)
at io.quarkus.deployment.steps.ConfigBuildStep$validateConfigProperties1249763973.deploy_0(ConfigBuildStep$validateConfigProperties1249763973.zig:158)
at io.quarkus.deployment.steps.ConfigBuildStep$validateConfigProperties1249763973.deploy(ConfigBuildStep$validateConfigProperties1249763973.zig:40)
at io.quarkus.runner.ApplicationImpl.doStart(ApplicationImpl.zig:642)
... 12 more`
After inspecting the code, I found that only the Quarkus validation fails to map these properties. My question is can we include the mapping in the validation procedure?
io.quarkus.arc.runtime.ConfigRecorder
is the class where the validation happens, i.e. validateConfigProperties method, which uses the load method from the same class. I think load method can be extended to accept java.util.Map as valid input, but I'm not yet familiar enough with Quarkus internals to try and provide fix by myself.
Additional note: I'm asking for this also as this mapping will be very useful in the future. Also documentation clearly states that
Quarkus relies on SmallRye Config and inherits its features.
and this is functional SmallRye Config feature.
This was tested on Quarkus 1.9.0.Final
@kristijan-rusu Thanks.
There was an issue causing the CNFE in some situations. Would you mind to override SR Config to 1.9.1 in your project and try it out?
@kristijan-rusu and @radcortez, it's great that we would be able to use maps in our configurations from version 1.9.1 onwards. Any idea when the release date of that version will be?
Also with regards to Multi Nested Lists, I assume it's something that would need to be catered for on SmallRye Config. The documentation does not explicitly state that. Any idea if this is the case as of now? If not, maybe an enhancement should be opened up against that project?
@kristijan-rusu, I tried using Maps and unfortunately it keeps failing. This is what I got:
application.yaml:
application:
services:
- service-a:
location: eur
url: https://service-a.com/about
environment: DEV
- service-b:
location: eur
url: https://service-b.com/about
environment: DEV
- service-c:
location: eur
url: https://service-c.com/about
environment: DEV
Config file:
@ConfigProperties(prefix = "application")
public class ServerConfiguration {
private Map<String, Service> services;
...
public static class Service {
private String location;
private String url;
private String environment;
...
}
}
I just keep getting the following error:
Caused by: java.lang.IllegalArgumentException: Nested configuration class 'java.util.Map' must contain a no-args constructor
Is it my configuration or is this not allowed in Quarkus/SmallRye Config
Yes, map support is provided by a SR feature detailed here:
https://smallrye.io/docs/smallrye-config/mapping/mapping.html
Which is a little bit different from Quarkus @ConfigProperties, so to have map support you need to use SR @ConfigMapping and convert the mapping class to an interface.
@kristijan-rusu and @radcortez, it's great that we would be able to use maps in our configurations from version 1.9.1 onwards. Any idea when the release date of that version will be?
SR Config 1.9.1 is already available in Quarkus 1.9.0. Unfortunately, it has this reported bug: https://github.com/smallrye/smallrye-config/issues/418, which was fixed in SR Config 1.9.2, so you need that one. This version does not work in native mode. It requires a Quarkus update done here: https://github.com/quarkusio/quarkus/pull/12789
Also with regards to Multi Nested Lists, I assume it's something that would need to be catered for on SmallRye Config. The documentation does not explicitly state that. Any idea if this is the case as of now? If not, maybe an enhancement should be opened up against that project?
Yes. Please open an issue in SR Config if you think we should support this.
@radcortez You are correct, I overrode SmallRye Config with version 1.9.2 and it works correctly as expected with SRC annotations. Maybe we can change the version in Quarkus for the next release? As the related issues are already fixed.
@ismailsalloo SmallRye Config uses different annotations, not Quarkus ones. This is a working example if you override SmallRye Config version, i.e. just put the following snippet in your Maven pom.xml:
<dependency>
<groupId>io.smallrye.config</groupId>
<artifactId>smallrye-config</artifactId>
<version>1.9.2</version>
</dependency>`
The mapping should look like this:
Configuration interface:
@ConfigMapping(prefix = "test.configuration")
public interface Configuration {
@WithName("enabled")
boolean enabled();
@WithName("config")
Map<String, Endpoint> config();
interface Endpoint {
@WithName("enabled")
boolean enabled();
@WithName("path")
String path();
}
}`
Properties:
test.configuration.enabled = true
test.configuration.config.testPathOne.enabled = true
test.configuration.config.testPathOne.path = /path/one
test.configuration.config.testPathTwo.enabled = true
test.configuration.config.testPathTwo.path = /path/two`
Or even shorter mapping:
@ConfigMapping(prefix = "test.configuration")
public interface Configuration {
@WithName("enabled")
boolean enabled();
@WithParentName
Map<String, Endpoint> config();
interface Endpoint {
@WithName("enabled")
boolean enabled();
@WithName("path")
String path();
}
}`
with the following properties:
test.configuration.enabled = true
test.configuration.testPathOne.enabled = true
test.configuration.testPathOne.path = /path/one
test.configuration.testPathTwo.enabled = true
test.configuration.testPathTwo.path = /path/two`
@kristijan-rusu thanks for the help :)
Thank you @kristijan-rusu and @radcortez for the assistance. I managed to get my application working using Maps
Great! Closing this and following up the original request in https://github.com/smallrye/smallrye-config/issues/436.