-
Notifications
You must be signed in to change notification settings - Fork 558
Compatibility and Versioning
This article is intended to guide you on best practices for making changes that are as backwards compatible as possible, and contains guidelines for how to introduce any necessary breaking changes safely.
Within the Fluid Framework, there are a few different package releases for different sections of the codebase. They are defined as follows:
- Server (protocol-definitions) ○ This is the structure of data over the network: ops, quorum, summary, etc. ○ Consumed by server, loader, drivers, runtime, etc.
- Driver (driver-definitions) ○ This is the driver API to be consumed by the loader and runtime
- Loader (container-definitions) ○ This is the container and loader API to be consumed by the runtime
- Client Runtime (runtime-definitions) ○ This is the runtime API to be consumed by the components
The server version is decided by the server, so we want to change protocol-definitions as infrequently as possible.
The driver and loader versions are decided by the hosting application. In the case of the web app, this will likely always be latest available, but other hosts may be fixed and need to load newer runtimes.
The runtime version is bound to the document. New versions are proposed within the document to the other clients and will automatically upgrade when accepted.
Changes to driver-definitions and loader-definitions need to be backwards compatible at least 1 version to previous runtime code and vice-versa.
Let's take a look at this table to see an example:
We can see that version 3.x of the loader needs to be compatible with version 2.x of the runtime, since that is within N-1 versions when N=3. However, it does not need to be compatible with version 1.x of the Loader.
We call this N/N-1 backwards compatibility.
When convenient, make changes that are backwards compatible and do not require API/interface changes.
Any changes to the @fluidframeworks/protocol-definitions package must be vetted highly. They will require a separate PR into the server project first anyway. We will want all changes to be backward-compatible.
Any changes to the @fluidframeworks/container-definitions package or the @fluidframeworks/driver-definitions package should be made backwards compatible with the runtime both ways.
- Changes to interfaces that are implemented by loader/container or driver objects: the implementations should satisfy the previous version's interface as well. This helps new loader work with old runtime.
- Changes to interfaces that are implemented by runtime objects: the implementations should satisfy the previous version's interface as well. This helps new runtime work with old loader. Changes to interfaces that represent data shared between runtime and loader: code referencing these should handle both the old format and the new. Typically this can be done at the point of first encountering that data to minimize downstream places to change. So the producing code should create something satisfying both the old and new interfaces, and the consuming code should convert old to new if needed when first seeing this object.
Any changes to the @fluidframeworks/runtime-definitions package should be made backwards compatible with external code (i.e. component code).
- Changes to interfaces that are implemented by the runtime objects: the implementations should satisfy the previous versions interface as well. The helps new runtime work with old component code.
- Changes to interfaces that represent data shared between runtime and external code: code referencing these should handle both the old format and the new. Typically this can be done at the point of first encountering that data to minimize downstream places to change. So the producing code should create something satisfying both the old and new interfaces, and the consuming code should convert old to new if needed when first seeing this object.
Whenever making a change that is backwards compatible, there are some practices to follow to keep changes the code consistent and clean. The following outlines how to make an API breaking change.
It is required to make the change such that it is backwards compatible in both directions. After two or more versions have passed, the change can be revisited to clean it up by removing the backwards compatible code. You can create a GitHub issue to track the removal of this code to ensure that it does not get lost in the changes and forgotten, as this will cause unnecessary code bloat.
It is nice to isolate the backwards compatible code as much as possible, rather than inline it. This will help make it clear to readers of the code that they should not rely on that code, and it will make removing it in the future simpler.
One strategy is to write the code as it should be without being backwards compatible first, and then add extra code to handle the old API.
Add comments to indicate important changes in APIs, for example if an API is deprecated, add a comment to indicate such.
In addition to isolating backwards compatible code, adding comments can also help identify all places to change when revisiting in the future. Using a consistent comment format can make it easier to identify these places in the future.
/**
** back-compat: 0.11 clientType
** TODO #{GitHub issue number}
*/The above format is nice, as it contains the version and a brief tag and is easy to find all references in the code later. It also contains the issue number so it makes it easy to reference where the code that needs to be removed for that issue lives.
As mentioned above, it is a good idea to track the follow-up work to remove this backwards compatible code to keep the code pruned. The code's complexity will creep up as more backwards compatible code comes in. A good strategy is to create a GitHub issue and include information that provides context and makes it easy for someone to cleanup in the future.
During the initial change, it is important to make sure the API changes are indicated somewhere in the docs. After making the follow-up change to remove the backwards compatible code, it should be documented in the BREAKING.md file so that it is clear that it will break.
This wiki is focused on contributing to the Fluid Framework codebase.
For information on using Fluid Framework or building applications on it, please refer to fluidframework.com.
- Submitting Bugs and Feature Requests
-
Contributing to the Repo
- Repo Basics
- Common Workflows and Patterns
- Managing dependencies
- Client Code
- Server Code
- PR Guidelines
- CI Pipelines
- Breaking vs Non-Breaking Changes
- Branches, Versions, and Releases
- Compatibility & Versioning
- Testing
- Debugging
- npm package scopes
- Maintaining API support levels
- Developer Tooling Maintenance
- API Deprecation
- Working with the Website (fluidframework.com)
- Coding Guidelines
- Documentation Guidelines
- CLA