Quarkus configuration is very similar to Spring:
- supports
application.propertiesandapplication.yaml - conventions on property names are the same: '.' as separator, kebab-case.
- supports property sources, iterating over them and getting value by name programmatically or with annotation
What's different:
- Quarkus uses smallrye-config which is implementation of microprofile-config. Annotation and feature-wise, it's similar to spring, but more explicit (reflection is discouraged)
- Property metadata can be extended with microprofile language-server extension (example)
- Doesn't support auto-binding 3rd party classes to config: requires explicit annotations and static binding.
What's (seems to be) the right approach for rich integration:
Micronaut configuration is similar to Spring too:
- supports
application.propertiesandapplication.yaml - conventions on property names are the same: '.' as separator, kebab-case
- supports property sources, iterating over them and getting value by name programmatically or with annotation
- IntelliJ supports
spring-configuration-metadata.json
What's different:
- Doesn't support auto-binding 3rd party classes to config: requires explicit annotations and static binding.
What's (seems to be) the right approach for rich integration:
- not clear
- AWS uses a @ConfigurationBuilder that happens to work with their typed client configuration. It depends on typed client-specific options and some duck typing (e.g. presence of
build()method on annotated with@ConfigurationBuilderoptions)
-
application.properties or application.yml
az.client.application-id=123 az.http.client.application-id=${az.client.application-id}321 az.appconfig.endpoint=https://lmolkova-appconfig.azconfig.io az.appconfig-secondary.endpoint=https://lmolkova-appconfig.azconfig.io
-
Implement configuration source
class SdkConfigurationSource implements ConfigurationSource { Map<String, String> properties; public SdkConfigurationSource() { properties = new HashMap<>(); ConfigProvider.getConfig().getConfigSources() .forEach(s -> s.getProperties() .forEach((k, v) -> properties.put(k, v))); } @Override public Set<String> getChildKeys(String path) { if (path == null) { return properties.keySet(); } return properties.keySet().stream() .filter(key -> key.startsWith(path) && key.length() > path.length() && key.charAt(path.length()) == '.') .collect(Collectors.toSet()); } @Override public String getValue(String propertyName) { return ConfigProvider.getConfig().getValue(propertyName, String.class); } }
-
register builder
public class SdkConfiguration { @Produces @ApplicationScoped ConfigurationBuilder sdkConfigurationBuilder() { return new ConfigurationBuilder(new SdkConfigurationSource()).root("az"); } @Produces @ApplicationScoped TokenCredential defaultTokenCredential(ConfigurationBuilder sdkConfigBuilder) { return new DefaultAzureCredentialBuilder().configuration(sdkConfigBuilder.build()).build(); } }
-
register SDK client builder
@Produces @ApplicationScoped @DefaultBean // in case there is another one ConfigurationClientBuilder configurationClientBuilder(ConfigurationBuilder sdkConfigBuilder, TokenCredential tokenCredential) { return new ConfigurationClientBuilder() .credential(tokenCredential) .configuration(sdkConfigBuilder.buildSection("appconfig")); }
-
named client
@Qualifier @Retention(RUNTIME) @Target({METHOD, FIELD}) public @interface Secondary { } @Produces @ApplicationScoped @Secondary ConfigurationClientBuilder secondaryConfigurationClientBuilder(ConfigurationBuilder sdkConfigBuilder, TokenCredential tokenCredential) { return new ConfigurationClientBuilder() .credential(tokenCredential) .configuration(sdkConfigBuilder.buildSection("appconfig-secondary")); }
@Path("/hello")
public class GreetingResource {
@Inject
ConfigurationClient appconfig;
@Inject @Secondary
ConfigurationClient appconfigSecondary;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Got configuration: " + appconfig.getConfigurationSetting("for", "bar").getValue() + ", secondary: " + appconfigSecondary.getConfigurationSetting("for", "bar").getValue();
}
}-
application.yml
az: http: client: application-id: test${appid} logging: log-level: body appconfig: endpoint: https://lmolkova-appconfig.azconfig.io appconfig-secondary: endpoint: https://lmolkova-appconfig.azconfig.io
-
implement configuration source
class SdkConfigurationSource implements ConfigurationSource { Set<String> properties; ApplicationContext appContext; public SdkConfigurationSource(ApplicationContext appContext) { this.properties = new HashSet<>(); this.appContext = appContext; this.appContext.getEnvironment().getPropertySources().stream() .forEach(source -> source.forEach(prop -> this.properties.add(prop))); } @Override public Set<String> getChildKeys(String path) { if (path == null) { return properties; } return properties.stream() .filter(key -> key.startsWith(path) && key.length() > path.length() && key.charAt(path.length()) == '.') .collect(Collectors.toSet()); } @Override public String getValue(String propertyName) { return appContext.getProperty(propertyName, String.class).orElse(null); } }
-
register configuration builder
@Factory public class SdkConfiguration { @Inject ApplicationContext applicationContext; @Singleton ConfigurationBuilder configurationBuilder() { return new ConfigurationBuilder(new SdkConfigurationSource(applicationContext)).root("az"); } }
-
register SDK client builder
@Factory public class AppConfiguration { @Inject ConfigurationBuilder sdkConfigurationBuilder; @Singleton @Named("appconfig-primary") // only needed if there is more than one client ConfigurationClient configurationClientBuilder(TokenCredential tokenCredential) { return new ConfigurationClientBuilder() .credential(tokenCredential) .configuration(sdkConfigurationBuilder.buildSection("appconfig")) .buildClient(); } @Singleton TokenCredential defaultTokenCredential() { return new DefaultAzureCredentialBuilder().configuration(sdkConfigurationBuilder.build()).build(); } }
-
named client
@Singleton @Named("appconfig-secondary") ConfigurationClient configurationSecondaryClientBuilder(TokenCredential tokenCredential) { return new ConfigurationClientBuilder() .credential(tokenCredential) .configuration(sdkConfigurationBuilder.buildSection("appconfig-secondary")) .buildClient(); }
@Controller("/hello")
public class HelloController {
@Inject
@Named("appconfig-primary")
ConfigurationClient appconfig;
@Inject
@Named("appconfig-secondary")
ConfigurationClient appconfigSecondary;
@Get
@Produces(MediaType.TEXT_PLAIN)
public String index() {
return "Got configuration: " + appconfig.getConfigurationSetting("foo", "bar").getValue();
}
}