Closed rmannibucau closed 4 years ago
defaultValue
+nullable
are just as interoperable as defaultValue
+Optional
. This combination is already supported and meaningful in Quarkus via SmallRye Config. It is not a question of priority because there should be no conflict, particularly in light of #446: if a user explicitly empties a value, then null
is injected even if there is a default value specified.
@Emily-Jiang hmm, do you assume we keep NULL_VALUE? otherwise it means nullable is only used if unconfigured if the default value I guess so likely your B option. A is not really an option since the annotation has default so both will exists, we can only check the actual value.
However, if we are breaking the API, there is another option: extract defaultValue and nullable as annotation, there is no more ambiguity and startup (build for graalvm friends ;)) time validation will be back with a clean programming model:
@Inject
@ConfigProperty
@DefaultValue("....")
String myUrl;
@Inject
@Nullable
@ConfigProperty
String myUrl;
both are valid but
@Inject
@ConfigProperty
@Nullable
@DefaultValue("....")
String myUrl;
this last one is not valid
Note that this option also enables to type the default which would be a huge win for a spec on top of CDI:
@Inject
@ConfigProperty
@DefaultValue.String("....")
String myUrl;
side note: @dmlloyd you mix config injection and value extraction from the injection point and configuration value materialization in Config, this thread is 100% about injection, not about value materialization, maybe you should open another topic?
The combination of nullable
and Optional
is not meaningful but does not have to be explicitly disallowed (it would not result in incorrect operation, just arguably useless behavior).
@Emily-Jiang hmm, do you assume we keep NULL_VALUE? otherwise it means nullable is only used if unconfigured if the default value I guess so likely your B option. A is not really an option since the annotation has default so both will exists, we can only check the actual value.
No, if you read the thread we're talking about introducing a nullable
parameter to @ConfigProperty
.
However, if we are breaking the API, there is another option: extract defaultValue and nullable as annotation, there is no more ambiguity and startup (build for graalvm friends ;)) time validation will be back with a clean programming model:
We weren't talking about breaking the API, but if you want to propose some of these changes for 2.0 I think that would make sense, at least as a proposal. I think some of the CDI stuff does need a bit more examination.
side note: @dmlloyd you mix config injection and value extraction from the injection point and configuration value materialization in Config, this thread is 100% about injection, not about value materialization, maybe you should open another topic?
You're assuming an injection implementation that is different from what we do. This isn't a topic, it's a proposal specifically to add a nullable
attribute, which was pretty well on track with a good degree of consensus to this point.
@dmlloyd nullable is not about materializing null for empty strings
if a user explicitly empties a value, then null is injected even if there is a default value specified
This is typically a bug, it should be an empty string (and it is used in some apps from what I saw, typically to set a prefix/suffix).
So we still have a topic to enable to specify null in the config source, this will likely pass through returning optional
Wdyt about the proposal of splitting in multiple annotations? It drops all ambiguity and is quite successful in JSON-B for example.
@dmlloyd nullable is not about materializing null for empty strings
if a user explicitly empties a value, then null is injected even if there is a default value specified
This is typically a bug, it should be an empty string (and it is used in some apps from what I saw, typically to set a prefix/suffix).
This is simply not the case universally. If you maintain this behavior, you create a large number of future problems - inconsistent array and list handling, potential problems relating to the future property expansion proposal, etc. In Quarkus we've successfully adopted the behavior specified by #446, which allows (for example) patterns like list.prop=a,${some.prop:},c
to return [ "a", "c" ]
when some.prop
is not set. Prefixes/suffixes are trivially achievable by way of Optional
and prefix.orElse("")
as appropriate.
It is not a question of priority because there should be no conflict, particularly in light of #446: if a user explicitly empties a value, then null is injected even if there is a default value specified.
If a user empties the property, the null
comes from the configsource. null
will be used. However, if the config property does not exist in any config sources and the defaultValue and nullable specified, which value should be assigned to the variable?
@rmannibucau the nullable is similar to your originally suggestion of nullAllowed
as shown in my code snippet. No additional annotation is introduced. I think the consensus is that we will remove NULL_VALUE and replace with an additional method
boolean nullable() default false;
@Inject @ConfigProperty(name="some", nullable=true) String value;
It is not a question of priority because there should be no conflict, particularly in light of #446: if a user explicitly empties a value, then null is injected even if there is a default value specified.
If a user empties the property, the
null
comes from the configsource.null
will be used. However, if the config property does not exist in any config sources and the defaultValue and nullable specified, which value should be assigned to the variable?
If the property does not exist in any config sources, then the default value would be assigned to the variable.
To clarify:
@Config @ConfigProperty(name="some", defaultValue="blah", nullable=true) String figure;
if the property some
does not exist in any config source, the value of figure
will be blah
not null
@Config @ConfigProperty(name="some", nullable=true) String figure;
if the property some
does not exist in any config source, the value of figure
will be null
In the spec, since nullable is another way to set defaultValue, we should specify the behaviour as above.
@emilyjiang Assuming @Config
means @Inject
above...
As far as which value to use for injection, the following should be equivalent:
@Inject
@ConfigProperty(name="some", defaultValue="blah", nullable=true)
String figure;
@Inject
@ConfigProperty(name="some", defaultValue="blah")
Optional<String> figure;
~You will never get a null
value or an empty Optional
injected.~ (depends on #446)
In cases where we are injecting into primitive values like:
@ConfigProperty(name="bla", nullable=true)
int bla;
This of course, doesn't make much sense. Should we just let the container ignore this or throw a DeploymentException saying that nullable
is not allowed for primitives?
@radcortez I was just about to comment on that. CDI specifies that null
injection to primitives results in the default value of the respective type. Not sure if that is a good idea here. I think I'm leaning towards disallowing nullable=true
when the injection target is a primitive. 🤷♂
Nullable means the user value is null and it is ok, injected value is the default of the type, no issue.
To clarify:
@Config @ConfigProperty(name="some", defaultValue="blah", nullable=true) String figure;
if the propertysome
does not exist in any config source, the value offigure
will beblah
notnull
@Config @ConfigProperty(name="some", nullable=true) String figure;
if the propertysome
does not exist in any config source, the value offigure
will benull
In the spec, since nullable is another way to set defaultValue, we should specify the behaviour as above.
nullable
must not be considered to be another way to set defaultValue
, because that eliminates the possibility to support the explicit erasure of property values described in #446.
Under this model, given a property with a default value and nullable=true
, there are three possibilities:
null
is usedSo as you see, we must consider nullable
(which is really just a variation on Optional
) to be orthogonal to default values.
No, your second point is a case we dont want null but "". Therefore you just demonstrated it is equivalent to defaultValue I think.
Resetting a value requires a specific value handled in configsources by Config, still nothing linked to this thread.
It's linked to this thread because, ideally, handling of the defatultValue
string should be the same as a string provided by any other config source. Thus, the motivation for avoiding the concept of a NULL_VALUE
sentinel until that is straightened out.
- The property is explicitly emptied in a configuration source, in which case
null
is used
Well, at this point, this doest not apply yet, since there is no way in the API to empty a configuration value. Only in the discussion here https://github.com/eclipse/microprofile-config/issues/446, right?
No, your second point is a case we dont want null but "". Therefore you just demonstrated it is equivalent to defaultValue I think.
First, I would request that you please not change my words. We want null
explicitly in this case; I meant what I said and I said what I meant.
But if you really believe that "" should be returned then you should be busy refuting #446, and also spend some time thinking about what should be returned if the property is an Integer
or a List
for example.
Resetting a value requires a specific value handled in configsources by Config, still nothing linked to this thread.
This concept of resetting a value is something that you have invented. What I've suggested is completely internally consistent within the specification, and is also implemented consistently and correctly in SmallRye.
Once again, this thread is about nullable
. If you want to suggest a different possibility, then please suggest it. I consider any attempt to "gaslight" or derail the conversation by stating altered facts as hostile behavior and a violation of the code of conduct and I will lock the thread if necessary to ensure that the discussion stays on topic.
- The property is explicitly emptied in a configuration source, in which case
null
is usedWell, at this point, this doest not apply yet, since there is no way in the API to empty a configuration value. Only here #446, right?
Correct, and in the SmallRye implementation. Once we have a few more cleanup items accomplished, I will be updating the implementation options for #446 to target 2.0.
Ok, I think we are getting to a conclusion. I've already implemented the changes, but before I've submit, we just need to decide on what to do with nullable=true
for primitives.
I think it is ok to allow it and use initialise the primitive with their default value.
@dmlloyd I think I glossed over the point @radcortez raised earlier - are you saying we need the ability to distinguish between 'property is not configured' and 'property is explicitly emptied by a config source' when considering whether to use defaultValue
?
@dmlloyd I think I glossed over the point @radcortez raised earlier - are you saying we need the ability to distinguish between 'property is not configured' and 'property is explicitly emptied by a config source' when considering whether to use
defaultValue
?
Yes, I do, even if only for the purpose of giving space to solve #446. If we do not do so, then solving #446 will require that this solution be revisited and probably changed.
Ok, I think we are getting to a conclusion. I've already implemented the changes, but before I've submit, we just need to decide on what to do with
nullable=true
for primitives.I think it is ok to allow it and use initialise the primitive with their default value.
I think this might muddy the waters further with respect to distinguishing an empty value (which would be forbidden for primitives) from a missing value (which would result in a default value of 0/false to be used for primitives). I'd rather see nullable
be forbidden completely for primitives for this reason, and for the simple reason that primitives cannot be null
, so why should they be allowed to be nullable
?
Let's keep it simple. What we want is:
As explained, 3 is about config-configsource contract so no link to this issue since this one is not about overriding a value to null but lztting null pass without a failure before, 1 and 2 are solved by NULL_VALUE already (or any of the - at least - 3 other concurrent API: nullable, placeholder, split annotation)
Yes, I do, even if only for the purpose of giving space to solve #446.
Then I think we should align defaultValue
behavior in @ConfigProperty
with a corresponding Config
method.
e.g.
<T> getValue(String propertyName, Class<T> propertyType, String defaultValue);
I think the CDI producer should be able to delegate directly to the Config API without special access to the underlying implementation.
That being said, I can't think of a use case where I would want to ever get null
returned from that method.
It goes further, as explained you must propagate it to the source with optional return types. Otherwise +1 for a getValue(ValueRequest) supporting all features of the api - we will add converters at some point, just wait ;).
Yes, but keeping it encapsulated behind Config
is a good idea, IMHO, otherwise the same rules need to be applied in the CDI layer.
Ok, I think we are getting to a conclusion. I've already implemented the changes, but before I've submit, we just need to decide on what to do with
nullable=true
for primitives. I think it is ok to allow it and use initialise the primitive with their default value.I think this might muddy the waters further with respect to distinguishing an empty value (which would be forbidden for primitives) from a missing value (which would result in a default value of 0/false to be used for primitives). I'd rather see
nullable
be forbidden completely for primitives for this reason, and for the simple reason that primitives cannot benull
, so why should they be allowed to benullable
?
Nevermind. I got the idea that CDI would inject the default value on a native if the value was null
via producer or something else. It does not:
https://docs.jboss.org/cdi/spec/1.1.EDR1/html_single/#null
It will just fail. So I guess we can specify that the implementation needs to validate and throw a DeploymentException stating that that combination is not supported. Or, we can just let the CDI implementation handle it and fail anyway.
@radcortez https://docs.jboss.org/cdi/spec/1.2/cdi-spec.html#primitive_types_and_null_values
Must have changed from 1.1. to 1.2 😛
Ops... that what happens when you search on Google for the spec and don't take the version into account. Silly me!
So, I believe @dmlloyd and @emattheis would prefer to disable nullable=true
for primitives. I think we should allow it to make it consistent with CDI. Any other thoughts?
Seems like maybe we're back to square one on this. The original request is really about avoiding the deployment time validation for non-optional types. Today, that is equivalent to checking for null from the underlying config and whether or not a default is supplied at the injection point. That may not be the case down the road when #446 is resolved.
So, optional=true
is confusing in light of Optional<T>
support, and nullable=true
lead us down this rabbit hole.
How about required=false
and we sidestep #446 concerns?
By default, required=true
so this fails at deployment if the property is missing (same as today):
@Inject @ConfigProperty(name="foo") String foo;
But this will not (even after #446):
@Inject @ConfigProperty(name="foo", required=false) String foo;
And is equivalent from a value perspective to this (today, and post #446):
@Inject @ConfigProperty(name="foo") Optional<String> foo;
@Inject @ConfigProperty(name="someint", nullable=true) int/Integer some;
if the property does not exist, maybe we should throw DeploymentException as nullable=true is not applicable.
`java.lang.OptionalDouble java.lang.OptionalInt java.lang.OptionalLong
These are the specialised Optional for primitive types. I don't think others exist.`
@radcortez nullable is different from Optional.
`java.lang.OptionalDouble java.lang.OptionalInt java.lang.OptionalLong
These are the specialised Optional for primitive types. I don't think others exist.` @radcortez nullable is different from Optional.
Sorry wrong issue, this was meant to #513
@emattheis @Inject @ConfigProperty(name="foo") String foo;
means that the property is mandatory. If you add defaultValue
, it will make it optional.
Sure, but we're back to arguing the semantics of optional. Now that I understand the motivations of #446 better, nullable=true
seems like the worst option 😛
If optionality dictates whether we validate at deployment time, then optional=true
makes perfect sense to me despite earlier objection in the thread above.
We can just specify that the default is optional=false
but it will be implicitly set to true
if the injection target is an optional type.
Think @emattheis has a point. Once core API is fixed, CDI layer would be simpler to do cleanly instead of doing the opposite. Let's try to fix our core then come back on this one.
I'm starting to think that we should retract this from 1.4 and actually do a breaking change for 2.0 and allow injection of null by default without any use of additional annotation configuration.
I agree Roberto, was an original error IMO, in particular when config can be reloaded these days (cloud etc).
That's a fundamental question of "avoid null in the API and CDI usages" i.e. "null is an anti-pattern" versus "avoid Optional in the API and CDI usages in favor of null" i.e. "Optional is an anti-pattern".
This is dangerously close to being a self-defeating, unending discussion in the vein of the "C vs C++" argument that has been raging on usenet for the past 20+ years. I've been around for long enough to know that neither side is "right" or "wrong". Traditionally this specification has favored Optional
for end-user APIs, and I think it makes sense for this reason to stay with this approach (considering also that Optional
fits in well with the JDK's streaming APIs and other usages, in addition to the historical usage in this specification).
If this specification had historically used null
instead, then I would have the opposite opinion. I don't think that hatred for null
or hatred for Optional
is sufficient to justify one or the other. I think that supporting both only adds a layer of confusion. Nevertheless as long as the proposal remained amicable I for one was willing to entertain it fairly. But so far the discussion has been quite contentious. Therefore I think that we should just proceed with the revert and try restarting the discussion later.
The revert is justified because the original change was made without appropriate process, so consensus is (IMO) no more needed for the revert than it was for the original change.
Sure, but we're back to arguing the semantics of optional. Now that I understand the motivations of #446 better,
nullable=true
seems like the worst option 😛If optionality dictates whether we validate at deployment time, then
optional=true
makes perfect sense to me despite earlier objection in the thread above.We can just specify that the default is
optional=false
but it will be implicitly set totrue
if the injection target is an optional type.
Can we at least agree that the semantics of nullable
(or whatever we call it) would be exactly the same as if the type of the property were Optional
? Which is to say, any case where an Optional
value would be empty
, a nullable
property would be null
?
Can we at least agree that the semantics of nullable (or whatever we call it) would be exactly the same as if the type of the property were Optional? Which is to say, any case where an Optional value would be empty, a nullable property would be null?
And if so, that goes to your point about whether or not we support both null
and Optional
in MicroProfile Config in general. That's certainly the crux of the issue since the original ask is to have the injection equivalent of Config#getValue
produce null
instead of throwing an exception at deploy time.
Also, for the record, I don't view this thread as overly contentious and I understand the problem space much better having participated. If my comments seem contentious to other participants, I do apologize, and thanks for everyone for sticking with it!
@rmannibucau I'm curious to know if your motivation for raising the issue was because an injected field was actually going to end up as null, or it was just null at deploy time and you need to bypass the validation?
The latter case is one I'm particularly interested in and it seems that maybe what we really need in the injection layer is not a null-handling solution, but a way to indicate whether deployment time validation is to be performed.
If we don't validate at deploy time, you have until the bean is instantiated to provide the config. Of course, we can't guarantee that NoSuchElementException
won't occur during injection, but we can't really guarantee that today anyway in the face of mutable sources.
Personally, I advocate for deploy time validation going away entirely.
Can we at least agree that the semantics of nullable (or whatever we call it) would be exactly the same as if the type of the property were Optional? Which is to say, any case where an Optional value would be empty, a nullable property would be null?
And if so, that goes to your point about whether or not we support both
null
andOptional
in MicroProfile Config in general. That's certainly the crux of the issue since the original ask is to have the injection equivalent ofConfig#getValue
producenull
instead of throwing an exception at deploy time.
Precisely. I contend that we do not need two distinct mechanisms for optionality. If the problem is deployment time validation, then let's solve that problem. If the problem is that we really hate Optional
and regret its usage in MP Config, let's tackle that directly instead.
In other words, let's make absolutely certain that this feature request is not a proxy for some other problem. It certainly seems like an XY problem the more we discuss it.
Again, either way we must revert this commit because it was merged without consensus.
I raised this issue for the validation blocker requiring a serious number of config entries to have a defaultValue=fakeDefault instead of just letting null being handled. Optional is not an option cause it is not serializable + half of the time didnt match the expected injection type - libraries signatures and i didnt want a postconstruct just to unwrap all my conf.
Have you tried using javax.inject.Provider
for your configuration properties?
It is not an option since by spec it requires too much work (resolution and reinstantiation, it is as redoing the injection) - no cache - so it is too slow for any runtime having some serious config. But it would have been for an one time startup configuration usage.
ok. Let's revert the change and do a white board design after Config 1.4 is out! I will move this out of the milestone release and tidy up the release note.
It must be allowed to inject that and get null, a workaround is to use an optional but it is not recommanded (even the opposite) to use optionals as fields so this is not a solution. Injecting null is fine from all point of views and compatible with mainstream config solution so it should probably make its path in mp-config asap.