-
Notifications
You must be signed in to change notification settings - Fork 15
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
Adopt AEP-180: Backwards Compatibility #191
Open
rambleraptor
wants to merge
3
commits into
aep-dev:main
Choose a base branch
from
rambleraptor:aep_180
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,246 @@ | ||
# Backwards compatibility | ||
|
||
**Note:** This AEP has not yet been adopted. See | ||
[this GitHub issue](https://github.com/aep-dev/aep.dev/issues/8) for more | ||
information. | ||
APIs are fundamentally contracts with users, and users often write code against | ||
APIs that is then launched into a production service with the expectation that | ||
it continues to work (unless the API has a [stability level][aep-181] that | ||
indicates otherwise). Therefore, it is important to understand what constitutes | ||
a backwards compatible change and what constitutes a backwards incompatible | ||
change. | ||
|
||
## Guidance | ||
|
||
Existing client code **must not** be broken by a service updating to a new | ||
minor or patch release. Old clients **must** be able to work against newer | ||
servers (with the same major version number). | ||
|
||
**Important:** It is not always clear whether a change is compatible or not. | ||
The guidance here **should** be treated as indicative, rather than as a | ||
comprehensive list of every possible change. | ||
|
||
There are three distinct types of compatibility to consider: | ||
|
||
1. Source compatibility: Code written against a previous version **must** | ||
compile against a newer version, and successfully run with a newer version | ||
of the client library. | ||
2. Wire compatibility: Code written against a previous version **must** be able | ||
to communicate correctly with a newer server. In other words, not only are | ||
inputs and outputs compatible, but the serialization and deserialization | ||
expectations continue to match. | ||
3. Semantic compatibility: Code written against a previous version **must** | ||
continue to receive what most reasonable developers would expect. (This can | ||
be tricky in practice, however, and sometimes determining what users will | ||
expect can involve a judgment call.) | ||
|
||
**Note:** In general, the specific guidance here assumes use of protocol | ||
buffers and JSON as transport formats. Other transport formats may have | ||
slightly different rules. | ||
|
||
**Note:** This guidance assumes that APIs are intended to be called from a | ||
range of consumers, written in multiple languages and with no control over | ||
how and when consumers update. Any API which has a more limited scope (for | ||
example, an API which is only called by client code written by the same team | ||
as the API producer, or deployed in a way which can enforce updates) should | ||
carefully consider its own compatibility requirements. | ||
|
||
### Adding components | ||
|
||
In general, new components (interfaces, methods, messages, fields, enums, or | ||
enum values) **may** be added to existing APIs in the same major version. | ||
|
||
However, keep the following guidelines in mind when doing this: | ||
|
||
- Code written against the previous surface (and thus is unaware of the new | ||
components) **must** continue to be treated the same way as before. | ||
- New required fields **must not** be added to existing request messages or | ||
resources. | ||
- Any field being populated by clients **must** have a default behavior | ||
matching the behavior before the field was introduced. | ||
- Any field previously populated by the server **must** continue to be | ||
populated, even if it introduces redundancy. | ||
- For enum values specifically, be aware that it is possible that user code | ||
does not handle new values gracefully. | ||
- Enum values **may** be freely added to enums which are only used in request | ||
messages. | ||
- Enums that are used in response messages or resources and which are | ||
expected to receive new values **should** document this. Enum values still | ||
**may** be added in this situation; however, appropriate caution **should** | ||
be used. | ||
|
||
**Note:** It is possible when adding a component closely related to an existing | ||
component (for example, `string foo_value` when `string foo` already exists) to | ||
enter a situation where generated code will conflict. Service owners **should** | ||
be aware of subtleties in the tooling they or their users are likely to use | ||
(and tool authors **should** endeavor to avoid such subtleties if possible). | ||
|
||
### Removing or renaming components | ||
|
||
Existing components (interfaces, methods, messages, fields, enums, or enum | ||
values) **must not** be removed from existing APIs in the same major version. | ||
Removing a component is a backwards incompatible change. | ||
|
||
**Important:** Renaming a component is semantically equivalent to "remove and | ||
add". In cases where these sorts of changes are desirable, a service **may** | ||
add the new component, but **must not** remove the existing one. In situations | ||
where this can allow users to specify conflicting values for the same semantic | ||
idea, the behavior **must** be clearly specified. | ||
|
||
### Moving components between files | ||
|
||
Existing components **must not** be moved between files. | ||
|
||
Moving a component from one proto file to another within the same package is | ||
wire compatible, however, the code generated for languages like C++ or Python | ||
will result in breaking change since `import` and `#include` will no longer | ||
point to the correct code location. | ||
|
||
### Moving into oneofs | ||
|
||
Existing fields **must not** be moved into or out of a oneof. This is a | ||
backwards-incompatible change in the Go protobuf stubs. | ||
|
||
### Changing the type of fields | ||
|
||
Existing fields and messages **must not** have their type changed, even if the | ||
new type is wire-compatible, because type changes alter generated code in a | ||
breaking way. | ||
|
||
### Changing resource paths | ||
|
||
A resource **must not** change its [path][aep-122]. | ||
|
||
Unlike most breaking changes, this affects major versions as well: in order for | ||
a client to expect to use v2.0 access a resource that was created in v1.0 or | ||
vice versa, the same resource name **must** be used in both versions. | ||
|
||
More subtly, the set of valid resource paths **should not** change either, for | ||
the following reasons: | ||
|
||
- If resource name formats become more restrictive, a request that would | ||
previously have succeeded will now fail. | ||
- If resource name formats become less restrictive than previously documented, | ||
then code making assumptions based on the previous documentation could break. | ||
Users are very likely to store resource names elsewhere, in ways that may be | ||
sensitive to the set of permitted characters and the length of the name. | ||
Alternatively, users might perform their own resource name validation to | ||
follow the documentation. | ||
- For example, Amazon gave customers [a lot of warning][ec2] and had a | ||
migration period when they started allowing longer EC2 resource IDs. | ||
|
||
### Semantic changes | ||
|
||
Code will often depend on API behavior and semantics, _even when such behavior | ||
is not explicitly supported or documented_. Therefore, APIs **must not** change | ||
visible behavior or semantics in ways that are likely to break reasonable user | ||
code, as such changes will be seen as breaking by those users. | ||
|
||
**Note:** This does involve some level of judgment; it is not always clear | ||
whether a proposed change is likely to break users, and an expansive reading of | ||
this guidance could ostensibly prevent _any_ change (which is not the intent). | ||
|
||
#### Default values must not change | ||
|
||
Default values are the values set by servers for resources when they are not | ||
specified by the client. This section only applies to static default values within | ||
fields on resources and does not apply to dynamic defaults such as the default IP | ||
address of a resource. | ||
|
||
Changing the default value is considered breaking and **must not** be done. The | ||
default behavior for a resource is determined by its default values, and this | ||
**must not** change across minor versions. | ||
|
||
For example: | ||
|
||
```proto | ||
message Book { | ||
// google.api.resource and other annotations and fields | ||
|
||
// The genre of the book | ||
// If this is not set when the book is created, the field will be given a value of FICTION. | ||
enum Genre { | ||
UNSPECIFIED = 0; | ||
FICTION = 1; | ||
NONFICTION = 2; | ||
} | ||
} | ||
``` | ||
|
||
Changing to: | ||
|
||
```proto | ||
message Book { | ||
// google.api.resource and other annotations and fields | ||
|
||
// The genre of the book | ||
// If this is not set when the book is created, the field will be given a value of NONFICTION. | ||
enum Genre { | ||
UNSPECIFIED = 0; | ||
FICTION = 1; | ||
NONFICTION = 2; | ||
} | ||
} | ||
``` | ||
|
||
would constitute a breaking change. | ||
|
||
#### Serializing defaults | ||
|
||
APIs **must not** change the way a field with a default value is serialized. For | ||
example if a field does not appear in the response if the value is equal to the | ||
default, the serialization **must not** change to include the field with the | ||
default. Clients may depend on the presence or absence of a field in a resource | ||
as semantically meaningful, so a change to how serialization is done for absent | ||
values **must not** occur in a minor version. | ||
|
||
Consider the following proto, where the default value of `wheels` is `2`: | ||
|
||
```proto | ||
// A representation of an automobile | ||
message Automobile { | ||
// google.api.resource and other annotations and fields | ||
|
||
// The number of wheels on the automobile. | ||
// The default value is 2, when no value is sent by the client. | ||
int wheels = 2; | ||
} | ||
``` | ||
|
||
First the proto serializes to JSON when the value of `wheels` is `2` as follows: | ||
|
||
```json | ||
{ | ||
"name": "my-car" | ||
} | ||
``` | ||
|
||
Then, the API service changes the serialization to include `wheel` even if the | ||
value is equal to the default value, `2` as follows: | ||
|
||
```json | ||
{ | ||
"name": "my-car", | ||
"wheels": 2 | ||
} | ||
``` | ||
|
||
This constitutes a change that is not backwards compatible within a major | ||
version. | ||
|
||
## Further reading | ||
|
||
- For compatibility around field behavior, see [AEP-203][]. | ||
- For compatibility around pagination, see [AEP-158][]. | ||
- For compatibility around long-running operations, see [AEP-151][]. | ||
- For understanding stability levels and expectations, see [AEP-181][]. | ||
- For compatibility with client library resource name parsing, see [AEP-4231][] | ||
- For compatibility with client library method signatures, see [AEP-4232][] | ||
|
||
<!-- prettier-ignore-start --> | ||
[aep-122]: ./0122.md | ||
[aep-151]: ./0151.md | ||
[aep-158]: ./0158.md | ||
[aep-181]: ./0181.md | ||
[aep-203]: ./0203.md | ||
[aep-4231]: ../client-libraries/4231.md | ||
[aep-4232]: ../client-libraries/4232.md | ||
[ec2]: https://aws.amazon.com/blogs/aws/theyre-here-longer-ec2-resource-ids-now-available/ | ||
<!-- 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: 180 | ||
state: reviewing | ||
state: approved | ||
slug: backwards-compatibility | ||
created: 2023-01-22 | ||
created: 2024-05-31 | ||
placement: | ||
category: best-practices |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move proto-specific guidance to a proto tab.