Implement versioning for REST API resources
Edit on GitHubIn the course of the development of your REST APIs, you may need to change the data contracts of API resources. However, you can also have clients that rely on the existing contracts. To preserve backward compatibility for such clients, we recommend implementing a versioning system for REST API resources. In this case, each resource version has its own contract in terms of data, and various clients can request the exact resource versions they are designed for.
Resources that are provided by Spryker out of the box do not have a version. When developing resources, only new resources or attributes are added without removing anything, which ensures backward compatibility for all clients. If necessary, you can implement versioning for built-in resources as well as extend the corresponding resource module on your project level.
To implement versioning for a REST API resource, follow these steps:
1. Implement ResourceVersionableInterface
To add versioning to a resource, the route plugin of the resource
module needs to implement not only ResourceRoutePluginInterface
, but also \Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\ResourceVersionableInterface
. The latter exposes a method called getVersion
that lets you set the resource version.
For more information on route plugins, see the Resource routing section in Glue Infrastructure.
Consider the following implementation of a route plugin:
CustomerRestorePasswordResourceRoutePlugin.php
<?php
namespace Spryker\Glue\CustomersRestApi\Plugin;
use Generated\Shared\Transfer\RestCustomerRestorePasswordAttributesTransfer;
use Generated\Shared\Transfer\RestVersionTransfer;
use Spryker\Glue\CustomersRestApi\CustomersRestApiConfig;
use Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\ResourceRouteCollectionInterface;
use Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\ResourceRoutePluginInterface;
use Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\ResourceVersionableInterface;
use Spryker\Glue\Kernel\AbstractPlugin;
/**
* @method \Spryker\Glue\CustomersRestApi\CustomersRestApiFactory getFactory()
*/
class CustomerRestorePasswordResourceRoutePlugin extends AbstractPlugin implements ResourceRoutePluginInterface, ResourceVersionableInterface
{
public function configure(ResourceRouteCollectionInterface $resourceRouteCollection): ResourceRouteCollectionInterface
{
$resourceRouteCollection
->addPatch('patch', false);
return $resourceRouteCollection;
}
public function getResourceType(): string
{
return CustomersRestApiConfig::RESOURCE_CUSTOMER_RESTORE_PASSWORD;
}
public function getController(): string
{
return CustomersRestApiConfig::CONTROLLER_CUSTOMER_RESTORE_PASSWORD;
}
public function getResourceAttributesClassName(): string
{
return RestCustomerRestorePasswordAttributesTransfer::class;
}
public function getVersion(): RestVersionTransfer
{
return (new RestVersionTransfer())
->setMajor(2)
->setMinor(0);
}
}
As you can see, the CustomerRestorePasswordResourceRoutePlugin
class implements the ResourceRoutePluginInterface
and ResourceVersionableInterface
interfaces. The resource supports only one HTTP method: PATCH
. Also, the getVersion
function sets version 2.0 for the resource:
Code sample:
class CustomerRestorePasswordResourceRoutePlugin extends AbstractPlugin implements ResourceRoutePluginInterface, ResourceVersionableInterface
{
...
public function getVersion(): RestVersionTransfer
{
return (new RestVersionTransfer())
->setMajor(2)
->setMinor(0);
}
}
Set both the major and minor versions of a resource; otherwise, requests to this resource fail.
2. Query specific resource version
After implementing a specific resource version, you can query the resource specifying the version you need. Send a PATCH
request to the /customer-restore-password
endpoint that now has version 2.0. The payload is as follows:
Code sample:
PATCH /customer-restore-password
{
"data": {
"type": "customer-restore-password",
"attributes": {
"email":"jdoe@example.com"
}
}
If \Spryker\Glue\GlueApplication\GlueApplicationConfig::getPathVersionResolving
is set to false, specify the exact version you need, in the HTTP header of your request:
Content-Type: application/vnd.api+json; version=2.0
If getPathVersionResolving
is set to true, then you have to set some value in \Pyz\Glue\GlueApplication\GlueApplicationConfig::getPathVersionPrefix
, “v” in our examples, and then your resource path should look like this:
PATCH /v2.0/customer-restore-password
In the preceding example, version 2.0 is specified. If you repeat the request with such headers, you receive a valid response with resource version 2.0. However, if you specify a non-existent version, for example, 3.0, the request fail.
Content-Type: application/vnd.api+json; version=3.0
In this case, the endpoint responds with the 404 Not Found
error.
Here’s a version matching rule-set:
PHP version:
(new RestVersionTransfer())
->setMajor(A)
->setMinor(B);
Then use version
In the header: Content-Type: application/vnd.api+json; version=A.B
In the path: /vA.B
PHP version:
(new RestVersionTransfer())
->setMajor(A);
Then, use version
In the header: Content-Type: application/vnd.api+json; version=A
In the path: /vA
There’s no fall-back to the latest minor, only exact match of version is used.
If a version is not specified, the latest available version is returned.
In order to call the the latest version of the resource, do not specify version in the request.
3. Add more versions
To implement a new version, you can create a new route plugin in your module—for example, to support version 3.0, you can use the following code in your plugin:
Code sample:
class CustomerRestorePasswordResourceRouteVersion3Plugin extends AbstractPlugin implements ResourceRoutePluginInterface, ResourceVersionableInterface
{
...
public function getVersion(): RestVersionTransfer
{
return (new RestVersionTransfer())
->setMajor(3)
->setMinor(0);
}
}
In the new plugin, you can configure routing differently. You can use a different controller class or use a different transfer for the resource attributes. See the following example:
Code sample:
...
public function getResourceAttributesClassName(): string
{
return RestCustomerRestorePasswordVersion3AttributesTransfer::class;
}
...
After implementing the plugin and the required functionality, you register the new plugin in Pyz\Glue\GlueApplication\GlueApplicationDependencyProvider
:
Code sample:
class GlueApplicationDependencyProvider extends SprykerGlueApplicationDependencyProvider
{
/**
* @return \Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\ResourceRoutePluginInterface[]
*/
protected function getResourceRoutePlugins(): array
{
return [
...
new CustomerRestorePasswordResourceRouteVersion3Plugin(),
];
}
You can add as many plugins as required by your project needs.
Thank you!
For submitting the form