-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Adopt Google's AIP-146 * Change 'service' to 'API'. * Update date and status * Minor content changes: 1. Add an example clarifying the primary general guidance about using the least generic option. 2. Refactored headers for protobuf vs. OAS. (Did not use tabs because the site generator breaks when headers are inside a tab.) * Link to future oneof AEP.
- Loading branch information
Showing
2 changed files
with
97 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,97 @@ | ||
# Generic fields | ||
|
||
**Note:** This AEP has not yet been adopted. See | ||
[this GitHub issue](https://github.com/aep-dev/aep.dev/issues/27) for more | ||
information. | ||
Most fields in any API, whether in a request, a resource, or a custom response, | ||
have a specific type or schema. This schema is part of the contract that | ||
developers write their code against. | ||
|
||
However, occasionally it is appropriate to have a generic or polymorphic field | ||
of some kind that can conform to multiple schemata, or even be entirely | ||
free-form. | ||
|
||
## Guidance | ||
|
||
While generic fields are generally rare, a API **may** introduce generic field | ||
where necessary. There are several approaches to this depending on how generic | ||
the field needs to be; in general, APIs **should** attempt to introduce the | ||
"least generic" approach that is able to satisfy the use case. | ||
|
||
For example, an API **should not** use a completely generic field (such as | ||
`google.protobuf.Struct` in protobuf APIs) when the value of the field must | ||
correspond to one of a known number of schemas. Instead, the API **should** use | ||
a [`oneof`](./oneof) to represent the known schemas. | ||
|
||
### Generic fields in protobuf APIs | ||
|
||
#### Oneof | ||
|
||
A `oneof` **may** be used to introduce a type union: the user or API is able to | ||
specify one of the fields inside the `oneof`. Additionally, a `oneof` **may** | ||
be used with the same type (usually strings) to represent a semantic difference | ||
between the options. | ||
|
||
Because the individual fields in the `oneof` have different keys, a developer | ||
can programmatically determine which (if any) of the fields is populated. | ||
|
||
A `oneof` preserves the largest degree of type safety and semantic meaning for | ||
each option, and APIs **should** generally prefer them over other generic or | ||
polymorphic options when feasible. However, the `oneof` construct is ill-suited | ||
when there is a large (or unlimited) number of potential options, or when there | ||
is a large resource structure that would require a long series of "cascading | ||
oneofs". | ||
|
||
**Note:** Adding additional possible fields to an existing `oneof` is a | ||
non-breaking change, but moving existing fields into or out of a `oneof` is | ||
breaking (it creates a backwards-incompatible change in Go protobuf stubs). | ||
|
||
#### Maps | ||
|
||
Maps **may** be used in situations where many values _of the same type_ are | ||
needed, but the keys are unknown or user-determined. | ||
|
||
Maps are usually not appropriate for generic fields because the map values all | ||
share a type, but occasionally they are useful. In particular, a map can | ||
sometimes be suited to a situation where many objects of the same type are | ||
needed, with different behavior based on the names of their keys (for example, | ||
using keys as environment names). | ||
|
||
#### Struct | ||
|
||
The [`google.protobuf.Struct`][struct] object **may** be used to represent | ||
arbitrary nested JSON. Keys can be strings, and values can be floats, strings, | ||
booleans, arrays, or additional nested structs, allowing for an arbitrarily | ||
nested structure that can be represented as JSON (and is automatically | ||
represented as JSON when using REST/JSON). | ||
|
||
A `Struct` is most useful when the API does not know the schema in advance, or | ||
when a API needs to store and retrieve arbitrary but structured user data. | ||
Using a `Struct` is convenient for users in this case because they can easily | ||
get JSON objects that can be natively manipulated in their environment of | ||
choice. | ||
|
||
If a API needs to reason about the _schema_ of a `Struct`, it **should** use | ||
[JSONSchema][] for this purpose. Because JSONSchema is itself JSON, a valid | ||
JSONSchema document can itself be stored in a `Struct`. | ||
|
||
#### Any | ||
|
||
The [`google.protobuf.Any`][any] object can be used to send an arbitrary | ||
serialized protocol buffer and a type definition. | ||
|
||
However, this introduces complexity, because an `Any` becomes useless for any | ||
task other than blind data propagation if the consumer does not have access to | ||
the proto. Additionally, even if the consumer _does_ have the proto, the | ||
consumer has to ensure the type is registered and then deserialize manually, | ||
which is an often-unfamiliar process. | ||
|
||
Because of this, `Any` **should not** be used unless other options are | ||
infeasible. | ||
|
||
### Generic fields in OAS APIs | ||
|
||
**Note:** OAS-specific guidance not yet written. | ||
|
||
<!-- prettier-ignore-start --> | ||
[any]: https://github.com/protocolbuffers/protobuf/tree/master/src/google/protobuf/any.proto | ||
[struct]: https://github.com/protocolbuffers/protobuf/tree/master/src/google/protobuf/struct.proto | ||
[JSONSchema]: https://json-schema.org/ | ||
<!-- prettier-ignore-end --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
--- | ||
id: 146 | ||
state: reviewing | ||
state: approved | ||
slug: generic-fields | ||
created: 2023-01-22 | ||
created: 2024-07-02 | ||
placement: | ||
category: fields |