smallrye-config icon indicating copy to clipboard operation
smallrye-config copied to clipboard

Allow optional ConfigMapping

Open marcelstoer opened this issue 1 year ago • 13 comments

Origin: https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/.E2.9C.94.20Make.20a.20whole.20.40configmapping.20interface.20optional.

In some cases it would be really helpful to declare a whole @ConfigMapping optional e.g. through @ConfigMapping(prefix = "my-app.my-component", optional = true). You could use this to make the existence/availability of a whole sub-tree of config properties conditional. When you inject the config mapping class into a bean it would then use Optional<MyComponentConfig> myComponentConfig rather than MyComponentConfig myComponentConfig.

Maybe...the most simple implementation would be to just not throw an exception if you don't find the property my-app.my-component.foo.bar if one of the segments refers to an optional @ConfigMapping (my-component in this example).

P.S. would be a great companion for #844 😄

marcelstoer avatar Nov 17 '22 07:11 marcelstoer

👍🏼 Often there's a configuration like

my-service:
   my-subsystem:
      prop1: value1
      prop2: value2
      prop3: value3            

And the configuration of my-subsystem is either completely mandatory or completely optional.

The following configuration example requires to define everything on the level my-service which means that it is impossible to use libraries that contribute to the project's configuration and forces me to have a single model of the complete configuration.

@ConfigMapping(prefix = "my-service")
interface MyService {
   Optional<MySubSystem> mySubsystem();
}

There should be a way to define such configuration mappings in libraries that are used in multiple projects without forcing them to be root configuration mappings. Quarkus has many such cases but Quarkus (e.g. all extensions share quarkus.). But extensions use a different configuration definition model.

renegrob avatar Nov 17 '22 17:11 renegrob

Maybe...the most simple implementation would be to just not throw an exception if you don't find the property my-app.my-component.foo.bar if one of the segments refers to an optional @ConfigMapping (my-component in this example).

If the entire my-component subtree is wrapped as an Optional inside the mapping it shouldn't throw an exception.

There should be a way to define such configuration mappings in libraries that are used in multiple projects without forcing them to be root configuration mappings. Quarkus has many such cases but Quarkus (e.g. all extensions share quarkus.). But extensions use a different configuration definition model.

Is this because of the validation that all subtrees must be mapped that is forcing you to have a single root tree with all sub-elements, even if optional?

radcortez avatar Nov 18 '22 16:11 radcortez

If the entire my-component subtree is wrapped as an Optional inside the mapping it shouldn't throw an exception.

That's the standard case where you have a (single) @ConfigMapping class for the entire application. That works. However, what I tend to do is 1 subsystem/component = 1 Maven module = 1 @ConfigMapping (per module). Hence, my applications have usually as many @ConfigMapping classes as there are subsystems/components.

marcelstoer avatar Nov 18 '22 18:11 marcelstoer

I think it makes sense in that if a group can be optional then a root should be able to be optional too, using the same (conceptual) logic. But there would have to be additional API to get a config mapping optionally e.g. Optional<MyService> optMyService = config.getOptionalConfigMapping(MyService.class), and maybe some other support as well. I fear that this might not be very easy to implement though.

dmlloyd avatar Nov 18 '22 19:11 dmlloyd

I have to say that I don't see much difference between having:

@Inject
Optional<MyMapping> mapping

interface MyMapping {
  Nested nested();

  interface Nested {
  }
}

(not supported)

or

@Inject
MyMapping mapping

interface MyMapping {
  Optional<Nested> nested();

  interface Nested {
  }
}

(supported)

When having multiple modules, each uses a mapping class, and each mapping class can map a single optional nested element for the subtree. Wouldn't that work?

radcortez avatar Nov 21 '22 14:11 radcortez

I agree that in your example there isn't much difference. However in this case:

@Inject
Optional<MyMapping> mapping

interface MyMapping {
  String: prop1();
  String: prop2();
  String: prop3();
}

vs

@Inject
MyMapping mapping

interface MyMapping {
  Optional<String> prop1();
  Optional<String> prop2();
  Optional<String> prop3();
}

There's a big difference: The first implementation validates that either all or none of the String values are present, while the 2nd implementation requires me to do the whole validation and mapping logic in the code. The 2nd implementation is like injecting the values using @ConfigProperty.

renegrob avatar Nov 30 '22 13:11 renegrob

Ok, let me see what I can do.

radcortez avatar Dec 01 '22 12:12 radcortez

Any news about that?

HerrDerb avatar Feb 23 '23 10:02 HerrDerb

No progress at the moment. Unfortunately, we are in the process of moving the main branches to jakarta and updating to Jakarta 10, so that is slowing us down on the enhancements side.

Hopefully, we should be done in the next few days so we can start moving with features.

radcortez avatar Feb 23 '23 20:02 radcortez