-
Notifications
You must be signed in to change notification settings - Fork 546
Snapshots and Resource Compatibility Checking
Due to the fact that resources and clients can be upgraded separately, it is very important to developers that they be notified of any changes they make that could break backwards or forwards compatibility issues. To that end, Rest.li uses a form of expanded IDLs, called Snapshots, to keep track of the state of resources and check compatibility between resource iterations.
The Snapshot Compatibility checker will be automatically run during a basic gradle build. However, if you wish to run the compatibility checker as a stand-alone on a particular target, you can do so by running gradle :[target]:checkRestModel
. Additionally, there are four compatibility levels that the checker can be run on, by adding the argument -Prest.model.compatibility=[compatibility-level]
to the gradle
command.
The compatibility checker generates .snapshot.json
files.
There are four levels of compatibility. From least permissive to most, they are:
equivalent If the check is run in equivalent mode, no changes to Resources or PDSCs will pass.
backwards Changes that are considered backwards compatible will pass, otherwise, changes will fail.
ignore The compatibility checker is run, but all changes will pass. All changes, backwards compatible and backwards incompatible, will be printed.
off The compatibility checker will not be run at all.
By default, the compatibility checker will be run on backwards compatibility mode. There are two ways to change the mode the compatibility checker is run on. For one, you can add a flag to the gradle build itself: --Prest.model.compatibility=<compatLevel>
Alternately, if you want to change the default compatibility level for all builds on a machine, you can create or edit the file ~/.gradle/gradle.properties
to contain the line rest.model.compatibility=<compatLevel>
Many developers are surprised that adding to an enum is considered a backwards incompatible change.
But, while Rest.li is designed with features to make it easier to add symbols to enums, it cannot possibly guarantee that adding a enum symbols is backward compatible.
To make it easier to add enums, when Rest.li generates a java enum for a enum data schema, it always generates a special "$UKNOWN" symbol. And whenever rest.li deserializes data that contains a symbol that is in the java enum, it deserializes it to the "$UNKNOWN" symbol. This gives readers of the enum the opportunity to check if the enum is "$UNKNOWN", and if it is, handle is in a special way.
However, it's still not possible to guarantee backward compatibility, even with the "$UKNOWN" symbol available. It's possible that clients are not written to handle the "$UKNOWN" symbol in a reasonable way. And even if they where somehow required to handle the "$UNKNOWN" symbol, it may be that they cannot do anything other than fail if they do not recognize a enum symbol. In many practical applications, it is not feasible to assess how all clients have been coded to handle new enum symbols, particularly when there are many clients.
If a few, very specific use cases, it may be that a API has only a single client, sometimes maintained by the same developers as the REST Resource implementation. In these very specific cases it may be easy for the developers to know that new enum symbols can be safely added at any time. But if additional client might be added in the future, it is still risky to get in the habit of adding enum symbols as-if they are backward compatible changes, because once additional clients start using the API, adding a symbol could break them.
Given all these potential issues with adding a enum symbol. It's important to think of adding enum symbols as backward compatible and, if a new symbols is to be added, establish a strategy for adding the enum symbol(s) just must be done for any other backward incompatible changes.
One possible strategy for adding a enum symbol might be:
- Add the symbol as a backward incompatible change to the data schema, use the rest.li gradle rest.model.compatibility flag to "ignore" the backward incompatible change. If you are using semantic versioning, you should also increment your MAJOR version number. Do NOT start writing data that contains the new enum value yet.
- Once the backward incompatible change has been published, REST API clients must be notified to the change. They should be provided with details on the enum symbol that has been added and how to migrate their applications. Clients should NOT write using the new symbol yet (the resource implementation may choose to reject requests to POST or PUT data with the new symbol).
- Only once all clients have migrated to the new (backward incompatible version) of the API, clients and the resource implementation may start writing data using the new symbol.
Some things to watch out for when adding enum symbols:
- Rest.li provides schema and data translation to avro. While the "$UNKNOWN" enum symbol works for rest.li data schema deserialization, it doesn't work for data persisted as Avro binary. Any attempt to deserialize an avro/binary record containing the new enumeration value with an older schema lacking that enum will fail. It is safe to ignore the incompatibility message if you are sure this will not happen, or you can work with your clients to make sure that it doesn't. In general, we only provide notifications about backwards incompatible changes as a tool for the developer. You are always free to ignore backwards-incompatible change messages if you know that the change will not cause problems, or are willing to take steps to ensure that it will not.
There are a number of error messages that can appear during compatibility checking. They will look something like this:
idl compatibility report between published "/publishedlocation/com.namespace.resource.snapshot.json" and current "/currentlocation/com.namespace.resource.snapshot.json":
Incompatible changes:
1) /location : detailed error message
This will tell you what resource is causing the problem, whether the change is backwards incompatible or not, and where exactly in the resource the problem is. You can then either use this information to fix the problem, or ignore it and re-run the build with a compatibility level that will cause it to pass.
The canonical snapshots for a set of resources will be located in the api project associated with those resources. If this project is located in some directory project-api, then the snapshots will be located in project-api/src/main/snapshot
. Similarly, canonical idls will be located in project-api/src/main/idl
. Though the files in these directories are generated, we strongly suggest that they be checked in. The IDLs must be checked in to correctly generate builders, and checking in the snapshots ensures that developers will receive accurate compatibility messages when making changes.
The temporary snapshots for a set of resources will be located within the same project as the java Resource files themselves. If the project for the Resource file is in a directory project-impl
, then the snapshots will be located in project-impl/src/mainGeneratedRest/snapshot
. Again, similarly, the temporary idls will be located in project-impl/src/mainGeneratedRest/idl
. Unlike the canonical files, these generated files should NOT be checked in.
The compatibility checker will also do limited IDL checks. By default, IDLs will only be checked to make sure there are no orphan IDLs from newly removed Resources and no missing IDLs from newly added Resources. A compatibility message may be printed saying that a resource has been added or removed. If a resource has been removed, you will need to remove the canonical IDL yourself. (This is also true for Snapshots).
If you are running a continuous integration environment on a rest.li project, you will want to run your compatibility checker on equivalent
. This will prevent your canonical IDLs and Snapshots from getting out of sync with the Resources they represent. You can do this either by running each build with the flag -Prest.model.compatibility=equivalent
, or by creating or editing the ~/.gradle/gradle.properties
file to contain the line rest.model.compatibility=equivalent
Quick Access:
- Tutorials
- Dynamic Discovery
- Request Response API
-
Rest.li User Guide
- Rest.li Server
- Resources
- Resource Types
- Resource Methods
- Rest.li Client
- Projections
- Filters
- Wire Protocol
- Data Schemas
- Data
- Unstructured Data
- Tools
- FAQ