Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empty values option 2 #445

Closed
wants to merge 4 commits into from
Closed

Conversation

dmlloyd
Copy link
Contributor

@dmlloyd dmlloyd commented Sep 6, 2019

This is the second option for an API to support the new consistent rules for empty value handling, by creating a fluent accessor API instead of using method overloads. Supercedes #407. Fixes #446.

@eclipse-microprofile-bot
Copy link
Contributor

Can one of the admins verify this patch?

1 similar comment
@eclipse-microprofile-bot
Copy link
Contributor

Can one of the admins verify this patch?

@dmlloyd
Copy link
Contributor Author

dmlloyd commented Sep 6, 2019

Before moving the PR out of draft:

@dmlloyd dmlloyd mentioned this pull request Sep 6, 2019
@Emily-Jiang
Copy link
Member

@dmlloyd You need to sort your ECA. The other thing - can you associate this PR with an issue. It might be better to open an issue to clearly specify the problem. It might make sense to copy some statement from PRs. When we release, we list issues. The closed unmerged PRs will be discarded.

@dmlloyd
Copy link
Contributor Author

dmlloyd commented Sep 9, 2019

ECA is sorted. I'll open an issue.

| `" "` | `String` | `getOptionalValue` | `Optional.of(" ")`
| `"foo"` | `String` | `getOptionalValue` | `Optional.of("foo")`
| missing | `String[]` | `getOptionalValue` | `Optional.empty()`
| `""` | `String[]` | `getOptionalValue` | `Optional.empty()`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Emily-Jiang
Copy link
Member

This approach is closer to what we have in master. With this, I think it might make better sense just deleting the snapshot and then reserve the copyright. It is not right to use the original idea but remove the original contribution. All contributors are listed under ConfigAccessor needs to be added. I think it is cleaner to delete unwanted methods and then rework. In this case, we can move faster and there won't be any copyright complication. Thoughts?

@Emily-Jiang
Copy link
Member

@dmlloyd You need to use -s to sign off. "David Lloyd (david.lloyd@****.com) did not include the "Signed-off-by footer" which is required for all commits made by a contributor."

@dmlloyd
Copy link
Contributor Author

dmlloyd commented Sep 11, 2019

The original accessor was builder-based and doesn't share a lot of similarities with the original in usage AFAICT. But I'm fine with adding contribution info, if the original author(s) of the original accessor approach want it.

I'll add the sign-off once the checklist above is complete.

@dmlloyd
Copy link
Contributor Author

dmlloyd commented Sep 11, 2019

I have a plausible implementation of the accessor API here: https://github.com/dmlloyd/smallrye-config/tree/accessor so that should be sufficient to demonstrate the workability of the API.

@dmlloyd
Copy link
Contributor Author

dmlloyd commented Sep 11, 2019

Also I don't like how the spec language is a bit inconsistent so I'll probably add commits to clean that up, either as part of this or as a separate PR. But I've filed #447 in any event.

@dmlloyd
Copy link
Contributor Author

dmlloyd commented Sep 16, 2019

I've updated this version (locally) to rename getAccessor to access (similarly to the original/existing version). But I won't push it up until #443 is resolved one way or another because I will need to rebase the whole thing (either way).

Copy link
Member

@Emily-Jiang Emily-Jiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to use the original builder pattern. @struberg please comment.

* @throws IllegalStateException if the accessor is optional, or if the accessor is already associated with a
* type-specific default value, or if there is no converter available for the given type
*/
<U> ConfigAccessor<U> convertedForType(Class<U> clazz) throws IllegalStateException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this pattern leads to the creation of ConfigAccessor being mutable or thrown away ConfigAccessor. This is why we introduced the builder pattern in the first place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect a properly tuned JDK to elide the vast majority of these allocations or else detect that they do not escape. The config accessor is not mutable (by specification) so I'm not sure what you mean by saying it's mutable.

The "fluent" style of API is widely used already. Using a builder on the other hand is much more unwieldy from a user's perspective, for something that will be thrown away immediately.

Copy link
Contributor Author

@dmlloyd dmlloyd Sep 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some other things to consider:

  • Using the fluent immutable API means the result is perfectly type safe. The builder approach means that no mutations are allowed which would change the type of the result (otherwise ugly tricks like unsafely re-typing the object would have to be employed). If (with the builder approach) you forbid re-typing, the type or converter must be passed in to access and cannot be changed later; this also means the accessor result must have separate methods for every result type (for example, optional values would need their own method, as would collection values (if those were ever added), etc.).
  • Using the builder API, you end up with an extra, strange build step. These two things together make the API more clunky and decrease its flexibility: config.access("some.prop", Foo.class).build().getOptionalValue() versus config.access("some.prop").withType(Foo.class).optional().getValue(). It's not entirely obvious that to the user that calling build is necessary, whereas with the immutable option, IDE completion would make it clear what operations are possible in a fairly unambiguous way based on the result type and possible method completions.
  • The fluent immutable API also reflects the immutability of the config object itself (not reflecting the mutability of the backing data, but the actual configuration object and its configuration from an API perspective).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with David, this pattern is fine and make things type safe and consistent.

cs = config.access("some.prop").withType(Foo.class).WithDefault("blah")
cs.getValue() -> "blah"
cs.WithDefault("Yes!") // <- this does not mutate the cs instance
cs.getValue() -> "blah"

// If users want to provide another default, he has to take the returned ConfigAccessor:
cs.withDefault("Yes!").getValue() -> "Yes!"

In most cases, users won't even keep a reference on the ConfigAccessor and will directly take the returned value for getValue().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's very similar to the original design and mixes up builder and ConfigAccessor. It's basically the old ConfigValue. We have been there over a year ago.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the builder pattern, which was in the master before. With the build pattern, the usage will be a functional style. You build one configAccessor and use it.

dmlloyd and others added 4 commits September 27, 2019 13:03
* [Spec] add section explaining how the Config API handles a config
  property with missing or empty value
* [Spec] mention that it is possible to set an empty value to reset a
  property from a lower-ordinal config source.
* [TCK] add EmptyConfigPropertyTest to test all cases of empty config
  property

Co-authored-by: David M. Lloyd <david.lloyd@redhat.com>
Signed-off-by: David M. Lloyd <david.lloyd@redhat.com>
@struberg
Copy link
Contributor

struberg commented Oct 1, 2019

@dmlloyd This is very, very similar approach to what was originally proposed over 3 years ago.
Please compare with https://github.com/struberg/javaConfig/blob/configValue/api/src/main/java/javx/config/ConfigValue.java

The design is derived from the work we've done in Apache DeltaSpike in 2011
https://github.com/apache/deltaspike/blob/master/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/ConfigResolver.java#L611

Copy link
Member

@Emily-Jiang Emily-Jiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the better approach is to use builder pattern. In this way, it is nice and clean. Just get hold of a builder and build an accessor. When building it, you can use different default or Converter.

@Emily-Jiang
Copy link
Member

@struberg The current code was influenced by the previous ConfigAccessor code, which was derived from the ConfigJSR conversation (DeltaSpike influcences).

@dmlloyd
Copy link
Contributor Author

dmlloyd commented Oct 1, 2019

@dmlloyd This is very, very similar approach to what was originally proposed over 3 years ago.

So I assume this means you don't have any objections to this approach.

Please compare with https://github.com/struberg/javaConfig/blob/configValue/api/src/main/java/javx/config/ConfigValue.java

One difference I see here is that it uses unsafe casting to use the same mutable instance, as opposed to returning a new typesafe instance.

The design is derived from the work we've done in Apache DeltaSpike in 2011
https://github.com/apache/deltaspike/blob/master/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/ConfigResolver.java#L611

Yes, this is very similar to my proposed approach.

@dmlloyd
Copy link
Contributor Author

dmlloyd commented Mar 5, 2020

Plan is to reopen this as new issues under the new organizational scheme.

@dmlloyd dmlloyd closed this Mar 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consistent empty/missing behavior
6 participants