It's more boilerplate but totally unambiguous and easier to document
and reason about, by far.
So question #1 - anyone want to argue this?
Question #2 - how far should this apply? Consider an "address" which
could be an IP, a CIDR, a name, or who knows what else in the future.
Would you say:
```
Type AddressType
Address string
```
or
```
Type AddressType
IP string
CIDR string
Hostname string
```
Lastly, we've never really locked down guidance (AFAIK) about the need
for discriminators. Originally we said it was so we could
unambiguously know the difference between "a one-of value was
specified that I don't comprehend" from "no value was specified". But
we're not at all consistent in the application of it, and for most
consumers the reaction to those two cases will be the same.
So question #3: discriminators or no?
Tim
On Fri, Apr 8, 2022 at 11:50 AM Rob Scott <robert...@google.com> wrote:
>
> Hey Everyone,
>
> I was just chatting with +Tim Hockin about Gateway API and our plans to graduate some resources to beta this month. We have several instances in our API that use a union discriminator field, such as HTTPRouteFilter. As far as I can tell, a union discriminator field is not strictly necessary but can be helpful. I can't find any clear guidance in our API conventions as far as if they should be used, and usage within existing Kubernetes APIs seems inconsistent.
>
> For Gateway API specifically, validation with a union discriminator has been a bit more difficult since it requires additional logic in our validating webhook (oneOf is helpful, but does not prevent someone from setting a field that is different than what the union discriminator indicates should be populated).
>
> I'm actually fine with either approach here. Since we already have union discriminators in some places in Gateway API, it would likely be most straightforward to continue with that pattern for the rest of the API, but want to get some recommendations from API reviewers for what we should do here.
>
> Thanks!
>
> Rob
>
--
You received this message because you are subscribed to the Google Groups "kubernetes-api-reviewers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kubernetes-api-rev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kubernetes-api-reviewers/CAO_Rewb76ARHXTwH6dsC0gqYPWzuKXu7RvWwOG48%3DunmdaiiRg%40mail.gmail.com.
I think in general, when designing APIs, dependencies between fields
(e.g. field A makes sense only if field B has a specific value, or in
your example "Only applies when Mode is Horizontal or Vertical.") gives
another opportunity for users to write invalid objects and should beavoided.
The pattern that you're proposing above (with HorizFrob, VertFrob, ...)
prevents wrong configuration from being authored. All the possible
options for an Horizontal Frob are constrained within that struct, so
one can't even make a mistake when they write their YAML (and/or don't
depend on the documentation as much). That's good.
If you apply the same logic to discriminator though, that's definitely
against my initial comment of dependencies between fields. What does it
mean to have Vertical Mode with an HorizontalFrob specified?
So all-in-all, I think the best would probably be to:
1. Avoid discriminators in general, since they give an opportunity to
author invalid configurations
2. Possibly, isolate members of a union within a struct, so they can
be easily cleared by clients (even if they don't know about new branches
of the union), e.g.
```
// This marker below means we only have union members in that struct:
// +union
type FrobMode struct {
// Horizontal specifies how horizontal frobbers operate.
Horizontal *HorizFrob
// Vertical specifies how vertical frobbers operate.
Vertical *VertFrob
// Periodic specifies how periodic frobbers operate.
Periodic *PeriodicFrob
// WARNING: Don't add unrelated fields here!
}
```
Unfortunately, it's sometimes impossible to know if a single field will
eventually become a member of a union.
3. Server-side apply should conflict properly and clear the other branch
of unions as needed,
4. The API server could clear unions for clients (especially for those
unions that couldn't be isolated) based on old/new object differences. This
means we could have multiple unions in the same struct which would be a
little ugly, but we can deal with it.
I think in general, when designing APIs, dependencies between fields
(e.g. field A makes sense only if field B has a specific value, or in
your example "Only applies when Mode is Horizontal or Vertical.") gives
another opportunity for users to write invalid objects and should be
avoided.
The pattern that you're proposing above (with HorizFrob, VertFrob, ...)
prevents wrong configuration from being authored. All the possible
options for an Horizontal Frob are constrained within that struct, so
one can't even make a mistake when they write their YAML (and/or don't
depend on the documentation as much). That's good.
If you apply the same logic to discriminator though, that's definitely
against my initial comment of dependencies between fields. What does it
mean to have Vertical Mode with an HorizontalFrob specified?
So all-in-all, I think the best would probably be to:
1. Avoid discriminators in general, since they give an opportunity to
author invalid configurations
2. Possibly, isolate members of a union within a struct, so they can
be easily cleared by clients (even if they don't know about new branches
of the union), e.g.
```
// This marker below means we only have union members in that struct:
// +union
type FrobMode struct {
// Horizontal specifies how horizontal frobbers operate.
Horizontal *HorizFrob
// Vertical specifies how vertical frobbers operate.
Vertical *VertFrob
// Periodic specifies how periodic frobbers operate.
Periodic *PeriodicFrob
// WARNING: Don't add unrelated fields here!
}
```
Unfortunately, it's sometimes impossible to know if a single field will
eventually become a member of a union.
3. Server-side apply should conflict properly and clear the other branch
of unions as needed,
4. The API server could clear unions for clients (especially for those
unions that couldn't be isolated) based on old/new object differences. This
means we could have multiple unions in the same struct which would be a
little ugly, but we can deal with it.
On Fri, Apr 22, 2022 at 2:24 PM Daniel Smith <dbs...@google.com> wrote:
Thanks for the great input here! It seems like there's widespread agreement to use type specific fields in a struct. Whether or not to use union discriminators seems somewhat more controversial. With that in mind, I made a Gateway API PR, that would use type specific fields + a union discriminator. My rationale for using a union discriminator is that:1. We already use a union discriminator elsewhere in the API.2. We can choose to make a union discriminator optional in the future, we can't retroactively add one.If we're 100% sure we want to avoid union discriminators in Kubernetes APIs, I'm fine with removing it from this, it just seems like it's still not completely clear.
If there's a discriminator, such clients can at least tell that there's an option selected that they don't understand, rather than just seeing nothing selected at all.
The ability to log good errors (e.g. "Unsupported frob type:
'Periodic'" vs "Unknown frob type") is nice but does it justify the
use of discriminators everywhere?
This is always the argument - can
we avoid one extra line of YAML?
The update problem seems resolvable as long as you have the before and
after, right? I know this is super subtle, so I am trying to think
thru all the skew possibilities.
It seems OK? Am I missing
something? It feels like we should document one-ofs as "if you see no
value in a one-of, assume it was something you don't understand".
Some trouble we had was with the verbosity imposed by discriminators. Since there are already type specific fields, it is rather odd for the user to also have to specify which type they're setting -- that ought to be implied by setting the type-specific fields:```// Feels redundant{ Type: A, A: MyConfigA{} }// This ought to be sufficient{ A: MyConfigA{} }```Also for CRDs (which as we understand all new API should start as CRDs before eventually being merged in) there's no easy way to ensure that the union discriminator and the type specific fields are in sync.
We can't do kube-apisever validation, and admission/validating webhooks are not considered a viable requirement to impose -- we can't ensure all users use the webhook.
To view this discussion on the web visit https://groups.google.com/d/msgid/kubernetes-api-reviewers/e290a1eb-16ba-4d13-9c3a-f3a7f97aa474n%40googlegroups.com.