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

Adopt AEP-180: Backwards Compatibility #191

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 244 additions & 3 deletions aep/general/0180/aep.md.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,246 @@
# Backwards compatibility
Copy link
Member Author

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.


**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 -->
4 changes: 2 additions & 2 deletions aep/general/0180/aep.yaml
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
Loading