# Field/Directive-based Versioning
Fields and directives can be independently versioned, and the version to use can be specified in the query through the field/directive argument versionConstraint
, and in the API endpoint through params fieldVersionConstraints[]
, directiveVersionConstraints[]
and versionConstraint
.
The engine follows this order to obtain the versioning for a field:
- Field argument in the query
- Endpoint param
fieldVersionConstraints[]
, on the namespaced type and field - Endpoint param
fieldVersionConstraints[]
, on the non-namespaced type and field - Endpoint param
versionConstraint
- Default versioning set in the requested schema
For directives it is similar, but in only 4 steps since they are not namespaced:
- Directive argument in the query
- Endpoint param
directiveVersionConstraints[]
, on the directive - Endpoint param
versionConstraint
- Default versioning set in the requested schema
If no versioning is found on any of these steps, then the field or directive is not versioned.
To select the version for the field/directive, GraphQL by PoP uses the same semver version constraints employed by Composer.
# Why
The evolution strategy adopted by GraphQL has a problem: when deprecating a field (as to replace it with a newer implementation), the new field will need to have a new field name. Then, if the deprecated field cannot be removed (for instance, because some clients are still accessing it, from queries that were never revised), then all these fields for a same functionality tend to accumulate in the schema, and the new implementation of the field will not have an optimal name (indeed, it will be worse than the deprecated field's name).
Evolution alone, over time, tends to pollute the schema with undesired definitions. Versioning the schema on a field/directive basis can solve this problem.
# Targeted versioning through the query
Pass the constraint to the field or directive, through argument versionConstraint
:
# Selecting version for fields
query {
#This will produce version 0.1.0
firstVersion: userServiceURLs(versionConstraint: "^0.1")
# This will produce version 0.2.0
secondVersion: userServiceURLs(versionConstraint: ">0.1")
# This will produce version 0.2.0
thirdVersion: userServiceURLs(versionConstraint: "^0.2")
}
# Selecting version for directives
query {
post(id:1) {
titleCase: title @makeTitle(versionConstraint: "^0.1")
upperCase: title @makeTitle(versionConstraint: "^0.2")
}
}
[View results: query #1, query #2]
# Targeted versioning through the endpoint
Provide the version constraints via the endpoint, through URL params, setting the combination of versioning for the elements in the schema. This allows to:
- Run instrospection queries against a granularly-defined version of the schema
- Have a client coded in TypeScript be generated by pointing to the exact required schema
- Point to a different version of the schema just by modifying the endpoint (eg: useful to wait for feature flags to kick in)
- Visualize a specific schema in GraphQL Voyager
The URL params to pass to the endpoint are:
fieldVersionConstraints[]
directiveVersionConstraints[]
These params are arrays, so they can be defined multiple times in the URL as to version more than 1 field or directive. Directives are referenced directly by their name (without the @
), like this:
?directiveVersionConstraints[makeTitle]=^0.1&directiveVersionConstraints[upperCase]=~0.2
Fields are referenced as a combination of their type and their name, separated with a dot, like Post.title
or User.name
:
?fieldVersionConstraints[Post.title]=0.3.1&fieldVersionConstraints[User.name]=0.2|0.3
The type name can be namespaced or not, it will work in either case, with automatic namespacing enabled or not.
Let's see some examples. In the links to execute the queries below, the URL parameters fieldVersionConstraints[]
and directiveVersionConstraints[]
were added to the GraphiQL URL, which then passes them to endpoint /api/graphql/
(you can check it out using the Firefox/Chrome DevTools' Network tab).
In this query, field userServiceURLs
is queried with version 0.1.0
set by field argument, and with version 0.2.0
by default, as set through URL param fieldVersionConstraints[Root.userServiceURLs]=^0.2
:
query {
versionByFieldArg: userServiceURLs(versionConstraint:"0.1.0")
versionByURLParam: userServiceURLs
}
...producing response:
{
"data": {
"versionByFieldArg": {
"github": "https://api.github.com/repos/leoloso/PoP"
},
"versionByURLParam": {
"github": "https://api.github.com/repos/getpop/component-model"
}
}
}
In this query, directive makeTitle
is queried with version 0.2.0
set by directive argument, and with version 0.1.0
by default, as set through URL param directiveVersionConstraints[makeTitle]=^0.1
:
query {
posts(limit:3) {
versionByURLParam: title @makeTitle
versionByFieldArg: title @makeTitle(versionConstraint:"^0.2")
}
}
...producing response:
{
"data": {
"posts": [
{
"versionByURLParam": "Scheduled By Leo",
"versionByFieldArg": "SCHEDULED BY LEO"
},
{
"versionByURLParam": "COPE With WordPress: Post Demo Containing Plenty Of Blocks",
"versionByFieldArg": "COPE WITH WORDPRESS: POST DEMO CONTAINING PLENTY OF BLOCKS"
},
{
"versionByURLParam": "A Lovely Tango, Not With Leo",
"versionByFieldArg": "A LOVELY TANGO, NOT WITH LEO"
}
]
}
}
Several fields and directives can be independently version, as in this query, which sets a different version for fields userServiceURLs
and userServiceData
, and directive makeTitle
.
# General versioning through the endpoint
Adding the versionConstraint
parameter in the GraphQL endpoint itself will define that version constraint in all fields. If any field does not support versioning, or doesn't satisfy that version constraint, will then ignore it.
For instance, this GraphiQL client has been versioned with constraint ^0.2
.
Any field can still override this default value with its own versionConstraint
. Running this query:
query {
#This will produce version 0.2.0
implicitVersion: userServiceURLs
#This will produce version 0.1.0
explicitVersion: userServiceURLs(versionConstraint: "^0.1")
}
Will produce different results, from the 2 different versions of the same field:
{
"data": {
"implicitVersion": {
"github": "https://api.github.com/repos/getpop/component-model"
},
"explicitVersion": {
"github": "https://api.github.com/repos/leoloso/PoP"
}
}
}
# Visualizing the schema for a specific version
TIP
In order to know which version of the field and directive is the one being used, GraphQL by PoP outputs the field's version as part of the field's description:
This is a temporary solution until GraphQL allows to query the extensions
field through introspection (over which the versioning can be made available).
GraphQL Voyager allows to visualize the schema for the different versions. In the default schema:
...field userServiceURLs
has the following signature, which corresponds to version 0.1.0
:
However, when adding ?versionConstraint=^0.2
to the URL (which in turn sets this parameter on the endpoint), we can visualize the schema for that version constraint. Then, field userServiceURLs
has this different signature, corresponding to version 0.2.0
: