eclipse / microprofile-config

MicroProfile Configuration Feature
Apache License 2.0
213 stars 115 forks source link

Null must be injectable #365

Closed rmannibucau closed 4 years ago

rmannibucau commented 6 years ago
@Inject
@ConfigProperty(name="...")
String value;

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.

Emily-Jiang commented 6 years ago

Do you mean you defined myConfig=

in config source, you will get empty string instead of null. Do you want to get null instead? If not, can you elaborate your use case?

rmannibucau commented 6 years ago

I'm not sure about myConfig=[nothing] case but more than myConfig doesn't exists and cdi bean uses the snippet I put in the first message, by default I expect it to be null instead of fail (required by tck today). I don't want (also can't in several cases) inject Optional cause it is not serializable and Provider is slow compared to have null.

Something like @ConfigProperty(name="...", nullAllowed=true) would be nice.

struberg commented 6 years ago

or optional=true

rmannibucau commented 6 years ago

sounds weird with Optional class no: "optional=true will set null and you can inject Optional which will not be null" :s? But agree a better phrasing can be found.

Emily-Jiang commented 6 years ago

-1 on optional=true, as it contradicts with the contract of "the presence of defaultValue" means optional.

How about to add a new constant on ConfigProperty?

String NULL_VALUE="org.eclipse.microprofile.config.configproperty.nullvalue";

Usage:

@Inject
@ConfigProperty(name="..." defaultValue=ConfigProperty.NULL_VALUE)
String value;
rmannibucau commented 6 years ago

I had that in mind as well so +1

dmlloyd commented 4 years ago

The currently proposed solution does not seem correct. One alternative discussed in #512 was to add a separate nullable annotation property indicating that the injected field should be treated as if it were optional with an orElse(null) qualification. This property would not be allowed to be true if the injection target is not nullable (that is, it is primitive).

radcortez commented 4 years ago

+1

rmannibucau commented 4 years ago

Hmm, why isnt it correct? Having a flag or a flag is the same. One is likely more elegant than the other but both are strictly equivalent. Now, for the primitive case, you must consider the magic string NULL_VALUE means "the configured value is null but passthrough" so the output takes its default. Overall point is that duplicating this notion just adds noise to the spec. Note, however, this is already outside the spec - like geronimo proxy support - since in practise using a default of NULL_VALUE is only valid in cdi injections so not primitives so we are safe anyway.

dmlloyd commented 4 years ago

Hmm, why isnt it correct? Having a flag or a flag is the same. One is likely more elegant than the other but both are strictly equivalent.

They are not strictly equivalent. How would you then specify the default value for a nullable property? If default values are implemented using a low priority configuration source (as we do in Quarkus), how do you represent this special behavior?

Bear in mind that we already have a recommended approach for optional values, and that is Optional.

Now, for the primitive case, you must consider the magic string NULL_VALUE means "the configured value is null but passthrough" so the output takes its default.

That is a bit of a stretch, I'd say. Anyway I believe I already proposed in #476 that primitive types should already implicitly default to their Java default values, which I think is probably a cleaner approach. It doesn't make these properties optional, but it does remove boilerplate.

Note, however, this is already outside the spec - like geronimo proxy support - since in practise using a default of NULL_VALUE is only valid in cdi injections so not primitives so we are safe anyway.

I'm not sure what you mean by this.

rmannibucau commented 4 years ago

How would you then specify the default value for a nullable property?

This is the whole point, you can't be both at the same time. If you have a default then you are not nullable so at the end it is just about requesting to accept null as a default. The NULL_VALUE (mp-config 1.4) is exactly there for this case.

Bear in mind that we already have a recommended approach for optional values, and that is Optional.

As explained before it goes partially against CDI and fully against java recommendation so it can't last IMHO.

The last part which was likely unclear is that the coercing of defaults for primitive is not needed in the spec, there is no way to inject a primitive with CDI so no need to spec how to do it. The only existing case it is useful is the case the impl supports configuration proxies (see an example here https://github.com/apache/geronimo-config/blob/trunk/impl/src/test/java/org/apache/geronimo/config/test/internal/ProxyTest.java#L104) but it is out of the spec for now.

dmlloyd commented 4 years ago

How would you then specify the default value for a nullable property?

This is the whole point, you can't be both at the same time. If you have a default then you are not nullable so at the end it is just about requesting to accept null as a default. The NULL_VALUE (mp-config 1.4) is exactly there for this case.

This is factually incorrect. Users can have a property which is optional/emptiable but also has a default value. If they want to explicitly clear the property, they may do so. At least, this works fine on SmallRye/Quarkus.

Bear in mind that we already have a recommended approach for optional values, and that is Optional.

As explained before it goes partially against CDI and fully against java recommendation so it can't last IMHO.

This should be tackled in a separate issue, since Optional is used extensively by this API for this purpose. Using this issue as a proxy for that discussion is counter-productive.

The last part which was likely unclear is that the coercing of defaults for primitive is not needed in the spec, there is no way to inject a primitive with CDI so no need to spec how to do it. The only existing case it is useful is the case the impl supports configuration proxies (see an example here https://github.com/apache/geronimo-config/blob/trunk/impl/src/test/java/org/apache/geronimo/config/test/internal/ProxyTest.java#L104) but it is out of the spec for now.

OK.

rmannibucau commented 4 years ago

Users can have a property which is optional/emptiable but also has a default value.

No, the meaning of defaultValue is "use that if not set". There is no way to set null anywhere in the configuration (getValue in a ConfigSource does not return an optional so no way to know it is null or not set at all). There is also no reason to set a default value if you support null. So at the end both cases are exactly the same: what value do you use if you get null from config sources.

What you are speaking about is another topic: how to specify null when there is a default value. In other words it is how to set explicitly null in/from the config source. This is another topic for the "core" API, not the CDI integration which uses NULL_VALUE as a marker in the @ConfigProperty annotation - as unconfigured marker which is not supported in the getValue/getOptionalValue methods. I don't think we want to use the constants of @ConfigProperty in Config to use them as marker + in the core API this distinction is not needed because you can set a default using getOptionalValue().orElse as easily as having a marker. So at the end, the injection API is covered now, and the standalone one too IMHO.

Using this issue as a proxy for that discussion is counter-productive.

Not sure I followed this one. Factually Optional fields do not work in passivable CDI beans so it prevents to use the spec and it is where this issue is coming from.

emattheis commented 4 years ago

There is no way to set null anywhere in the configuration (getValue in a ConfigSource does not return an optional so no way to know it is null or not set at all).

This issue has been raised separately and needs to be resolved, IMHO. Personally, I would like to see the empty string "" used to indicate the presence of a property with an absent value in a config source, as distinct from a null which means the property is not present and other sources should be considered.

The last part which was likely unclear is that the coercing of defaults for primitive is not needed in the spec, there is no way to inject a primitive with CDI so no need to spec how to do it.

Primitives types are valid beans and injectable per CDI. CDI also specifies that null gets coerced to a primitive's default at injection time. It's true they are not proxyable, but that should not be a concern for MicroProfile Config.

emattheis commented 4 years ago

The debate about how to indicate nullability in the annotation is largely driving by limitations in the expressiveness of annotation structures. I think the guiding principle here is that we need to avoid further complicating the interpretation of value strings by overloading the use of the defaultValue annotation field to convey addition meaning.

It can certainly be argued that this is ambiguous at a glance:

@ConfigProperty(name = "foo", nullable = true, defaultValue = "bar")
String foo;

But so is this:

@ConfigProperty(name = "foo", defaultValue = "bar")
Optional<String> foo;

It is easy enough to specify that defaultValue must be empty when nullable is true, and CDI gives us a convenient way to detect that condition at bootstrap time and provide meaningful feedback.

emattheis commented 4 years ago

Maybe the real issue here is the defaultValue support itself. Config doesn't have

<T> T getValue(String propertyName, String defaultValue, Class<T> propertyType);

So why should we provide such convenience during injection? As recommended in the javadocs, a developer is almost certainly better off simple bundling the default values in META-INF/microprofile-config.properties.

dmlloyd commented 4 years ago

The debate about how to indicate nullability in the annotation is largely driving by limitations in the expressiveness of annotation structures. I think the guiding principle here is that we need to avoid further complicating the interpretation of value strings by overloading the use of the defaultValue annotation field to convey addition meaning.

+1 here

It can certainly be argued that this is ambiguous at a glance:

@ConfigProperty(name = "foo", nullable = true, defaultValue = "bar")
String foo;

But so is this:

@ConfigProperty(name = "foo", defaultValue = "bar")
Optional<String> foo;

It is easy enough to specify that defaultValue must be empty when nullable is true, and CDI gives us a convenient way to detect that condition at bootstrap time and provide meaningful feedback.

We support this case. Having a default is not the same as making a property be required (if you assume the reverse, all kinds of ambiguous situations appear, as previously discussed).

Maybe the real issue here is the defaultValue support itself. Config doesn't have

<T> T getValue(String propertyName, String defaultValue, Class<T> propertyType);

This has been proposed. But using a config source is more future-proof (calling back to the property expansion discussion).

rmannibucau commented 4 years ago

@emattheis the default value is handled in the programmatic API through getOptionalValue. Being programmatic, the dev is responsible for making it efficient and well implemented. Being said the mainstream way to get injections is fields, it must already resolve the defaults for performances reasons. It is also inspired from the 3-4 implementations which created MP-config (deltaspike, sabot and friends) which all ended up doing that cause it was the mainstream case.

dmlloyd commented 4 years ago

@emattheis the default value is handled in the programmatic API through getOptionalValue.

This is just not generally true.

emattheis commented 4 years ago

Having a default is not the same as making a property be required (if you assume the reverse, all kinds of ambiguous situations appear, as previously discussed).

Seems like there are just three cases to worry about for injection:

  1. the programmer wants deployment to fail when the property is not configured
  2. the programmer wants to be guaranteed a value, even when the property is not configured
  3. the programmer is prepared to handle null or Optional

This has been proposed. But using a config source is more future-proof (calling back to the property expansion discussion).

Agree. I am certainly not advocating for its introduction, just arguing for @ConfigProperty to follow semantically with the Config API.

emattheis commented 4 years ago

the default value is handled in the programmatic API through getOptionalValue. Being programmatic, the dev is responsible for making it efficient and well implemented.

Precisely my point. The same option is available post-injection in CDI.

It is also inspired from the 3-4 implementations which created MP-config (deltaspike, sabot and friends) which all ended up doing that cause it was the mainstream case.

Sometimes following the leader isn't the best way to go 😛. IMHO, hardcoded defaults in source code is an anti-pattern to be avoided.

rmannibucau commented 4 years ago

Well it is key to have a standard way to set the default and dont rely on a coded one. It normalizes the config interaction, to understand the config without digging into any code and enables to generate doc trivially. It would be a huge regression to be programmatic only and would make the injection almost useless and rewrapped in a custom api as before it exists (if apps dont move back to deltaspike).

emattheis commented 4 years ago

It would be a huge regression to be programmatic only and would make the injection almost useless and rewrapped in a custom api as before it exists (if apps dont move back to deltaspike).

Do you actually have data to support this claim? This is a common theme that comes up around breaking changes. It's tempting to look at an existing API and say that any breaking change would be a large impact, but that assumes users are heavily invested in the feature you are breaking. I'm genuinely curious how we gauge the usage of features and weigh the impact of breaking changes.

That being said, if we removed defaultValue from the annotation, anyone using it would simply need to move the value to META-INF/microprofile-config.properties. I'm not sure how that would make injection "almost useless" or compel someone to move back to deltaspike.

dmlloyd commented 4 years ago

This issue is purely about allowing users to specify that they want a property to be optional without using an explicit Optional.

So the only questions that matter are:

radcortez commented 4 years ago

IMO

  • Should we allow users to do this at all? Yes

  • Is there any reason that a boolean nullable property cannot be supported? No

  • Is there any problem with making the default value of this property be false? No

rmannibucau commented 4 years ago

No @dmlloyd, the issue is about enabling null to be injected - as you said - but not to duplicate an existing api. Note that I think NULL_VALUE solves that already so this issue is fixed I guess.

Side note: on your question about how do we gauge the usage: i guess we all use our own experience. On my side note a single app wpuld work dropping that feature. Moving to a properties file require new tooling too (for fatjar and living doc for example to cite just 2).

emattheis commented 4 years ago

Note that I think NULL_VALUE solves that already so this issue is fixed I guess.

Only on an unreleased version that is now receiving scrutiny by implementors since we're at RC-2.

My apologies for muddying the waters with the defaultValue discussion. @rmannibucau are you firmly opposed to the nullable field instead of the sentinel value?

rmannibucau commented 4 years ago

Hmm RC1 - not a beta - is released and has NULL_VALUE so guess game is kind of played for this year.

I'm strongly against duplicating any information and/or making it ambiguous.

In other words, nullable=true would be ok if NULL_VALUE is removed since then chain flow is unique (if default is configured else if nullable else fail) IMHO.

radcortez commented 4 years ago

I think this is the proposal: replace NULL_VALUE with nullable = true.

rmannibucau commented 4 years ago

RC1 and RC2 are final releases (RC are not supposed to have breaking changes) so I'm -1 (as an user so not strictly blocking). Microprofile already has a bad image cause of the breaking changes it does so guess we should learn to limit it as much as possible, in particular when it is just about semantic and not feature related.

emattheis commented 4 years ago

In other words, nullable=true would be ok if NULL_VALUE is removed since then chain flow is unique (if default is configured else if nullable else fail) IMHO.

I think we’re in agreement there. @dmlloyd ’s request was specifically to revert the NULL_VALUE work, so we would replace that with nullable property.

As far as RC semantics go, I was not aware of any alpha or beta release - where does one evaluate those?

kenfinnigan commented 4 years ago

@rmannibucau is there a definition that precludes a feature that's present in an RC from being removed in a subsequent RC?

My understanding was that an RC is a possible release candidate for the specification, not a final release. If in evaluating the RC it becomes apparent that a feature has a problem, I don't see it being an issue to remove or adjust a feature between RC releases if necessary.

rmannibucau commented 4 years ago

Seems RC is not very well defined (I'm using https://wiki.eclipse.org/MicroProfile/SpecRelease/Release as a ref). My understanding, and how I interpret it is:

  1. RC is used cause there is a vote like process @eclipse so it needs some kind of staging before pushing finals, RC are just materializing it
  2. RC are released before the final with no structural (breaking) change compared to the final

Overall point is that implementors can always use snapshots to evaluate API changes so guess this definition is fine but also agree it can need to be made more explicit (likely another ticket ;)).

kenfinnigan commented 4 years ago

Yeah, it was raised the other day that we haven't voted on spec releases in MP for ages. I think it's been put on hold until the Working Group decision is made because the spec process there would define the voting.

For reference, I recall at least one specification in the past that made breaking changes between RC releases due to issues that came about in verifying an RC.

rmannibucau commented 4 years ago

Hmm, I wouldnt say that because it had been done it should be redo.

Let's try another approach: is the 1.4 needed? Can't we go 2.0 directly adding a few other tickets (maybe proxying API to cite one)? There the breaking change would be less an issue I guess. Alternatively it can go in 2.0.

Just to highlight a few points: having specs having breaking changes breaks the full ecosystem in several environments, OSGi is one coming to my mind but it is not the only one. This shouldn't be underestimated because the immediate linking we are thinking about is the classpath because it requires a lot of effort after to catch up for apps (I ignore here the vendor effort but it is real too).

radcortez commented 4 years ago

This one was blocking a few things for implementors: https://github.com/eclipse/microprofile-config/issues/390

radcortez commented 4 years ago

BTW, if there is no 1.4, there is no breaking change.

rmannibucau commented 4 years ago

There are 2 1.4 - RC is when it starts to be consummed, beta and alpha are generally ignored. Guess you (= eclipse members) can access the stats on central so see how much it got already consumed to judge more precisely.

kenfinnigan commented 4 years ago

I don't agree that it's a breaking change to alter a feature that was only present in the master branch previously. My understanding is that the Null behavior wasn't present in 1.3, so whether it is or isn't ultimately in 1.4 it doesn't impact 1.4 being a non-breaking change release.

Are you saying that once an RC is cut there is absolutely no opportunity to say a mistake was made and a feature needs to change? If so, that's not how MicroProfile specifications have been working to date

radcortez commented 4 years ago

Even if this is the case, the RC was cut a couple of days ago. To my knowledge, there is no release from any implementor with this version yet, so no end users using it right now.

emattheis commented 4 years ago

Also, RC1 and RC2 came on the same day, with nothing prior to that since May 2018 🤔 If there is a plan for alpha or beta releases, I'd love to know.

@rmannibucau I can appreciate your frustration since it looks like you filed this back when 1.3 was released. Personally, I wasn't aware of it until recently.

rmannibucau commented 4 years ago

Well, this thread wouldnt have popped out, apache would be releasing a 1.4 version. It is on hold cause of this - and note there is no consumer issue with the snapshot and this issue is cleanly solved too (updated 2 apps with no issue but all null workarounds removed).

If RC are not RC then please 1. call them alpha to remove any ambiguity and add a warning for users that it is unstable and must not be used (what a SNAPSHOT is semantically) 2. write a blog post about it to clearly spread the work cause it is not what RC are commonly.

emattheis commented 4 years ago

It seems premature for an implementation to release a version based on a release candidate of the spec. We can argue about what sort of changes can be made during a RC cycle, but the whole point is to have the chance to make them, right?

rmannibucau commented 4 years ago

Hmm, dont you mix milestones where, I agree, goal is to test, and RC where goal is to do a final but due to immutable state of releases RC are used in case something unexpectedly bad is found (and then final is just a copy of the first "successful RC") - can be a bad manifest, bad bytecode version and so on, but not a design issue?

kenfinnigan commented 4 years ago

It might be that we need to adjust the spec release process to actually do a Beta first to indicate "feature complete" but not candidate release.

In the past, and present, that's been problematic as releases tend to be right before an MP platform release, but it's something we should look at

radcortez commented 4 years ago

Anyway, are we reaching a conclusion here? Are we all in agreement to rewrite the feature like @dmlloyd suggested?

rmannibucau commented 4 years ago

If all actions are taken at the same time (code change but more importantly making it explicit through the process doc page what RC should be - I don't care you qualify them as "beta" or decide to keep the RC semantic and to do milestones instead + writing somewhere that both RC are "beta" - I let native speakers find more appropriated words) no issue on my side to revert/add nullable.

radcortez commented 4 years ago

I think that is reasonable. @Emily-Jiang what do you think?

Emily-Jiang commented 4 years ago

I finally caught up with the conversation.

Emily-Jiang commented 4 years ago

I thought about this further: Since we have two ways to specify default value now, we need to either

@Inject @ConfigProperty(name="someProp", defaultValue="me", nullable=true) String somePropVaue Thoughts?