Present GraphQL schema variants that match the API feature lifecycle (pre-release, released, future-breaking)
Problem
Our GraphQL API is a single-version API, which makes the following negotiations tricky as they happen within the same active API:
- Our ability to add experimental features without releasing them.
- Our ability to remove released features in future after a deprecation cycle.
- The public's ability to use a reliable and stable API.
Current situation
We implement the following aspects in pieces, but those pieces lack overall consistency or a cohesive UX.
- Pre-release features are marked as deprecated which discourages their use but they are present in the released schema.
- Future-breaking can be observed with a query param
remove_deprecated=true
which removes deprecated features (and also pre-release features due to their leverage of deprecations).
Proposal
We should simplify the experience for users and present our single API as different lifecycle variants.
Each variant would present altered views of our single schema. There would continue to be only a single schema that we maintain under the hood. The views would use the existing deprecated
and alpha
metadata already associated with our schema items to determine visibility, in combination with the graphql-ruby
gem's #visible?
method.
graph TD
C(Pre-release) --> B
A(Released) --> B[Single schema]
D(Future-breaking) --> B
The three variants would be:
Variant | Audience | Schema view |
---|---|---|
Released | The public for use in production. | Contains only released features (includes released deprecated features). |
Pre-release | GitLab itself. | Contains released and pre-release (experimental) additive features. Discouraged for public production use as could change at any time. |
Future-breaking | The public for use in testing, staging or QA environments. | Contains released features with all deprecated features removed. |
Engineers would move schema features through these lifecycle variants using the same metadata that they currently use; with alpha
and deprecated
.
Option 1: Header
-
X-GITLAB-GRAPHQL-VARIANT
: optional to enable pre-release or future-breaking variants. Values: (experimental
,future-breaking
)
Our version of GraphiQL Explorer doesn’t allow editing of headers in the UI, but I believe current versions do. We could alternatively mount GraphiQL at different URIs (below).
Named alpha sets
Opt-in by header could happen either by specifying specific sets of alpha changes, if we were to require developers to name or id them, or to all alpha changes at once.
Example:
X-GITLAB-GRAPHQL-VARIANT: "15_10_AuthorValues,15_11_UserSearchByStatus"
Option 2: URIs
We could alternatively, or additionally, express the variants with different URIs:
api/graphql
api/graphql/experimental
api/graphql/future-breaking
We could mount GraphiQL at corresponding URIs /-/graphql-explorer/{,experimental,future-breaking}
.
Benefit
The variants will have these benefits to the public:
- Present a cohesive experience for users to interact with GraphQL features at different lifecycle points.
- Clarify how working with a single version API can be a predictable experience.
And these benefits to GitLab:
- Make it easier for us to communicate to GraphQL users about our API's lifecycle with regard to how the public should interact with it.
Competitors
GitHub
GitHub offers schema previews to enable experimental features. These are well-documented features that the public is encouraged to beta-test and provide feedback on. These use an Accept
header with named features to enable. As GitLab's pre-release features would be non-breaking additive features, there's little benefit in enabling granular feature opt-in versus enabling all features.
GitHub doesn't appear to offer a future-breaking preview.
Implementation
Technically it would be reasonably simple, as our API schema items are already marked with alpha
and deprecated
metadata. We would use the metadata in combination with the graphql-ruby
gem's #visible?
method to determine visibility. This is already how the remove_deprecated=true
query param works.