.NET

How to Handle API breaking changes

Pinterest LinkedIn Tumblr

We finally deployed our first API version! Some clients have already started using the API. This bring us with excitement knowing that something we build is bringing value to others, but also creates a feeling that we should be more careful with changes from now on.

We know that eventually new requirements will come up, and we will have to make changes in the API that will definitely break some things in consumer’s side. The solution to this problem is to implement a versioning system in our API. In the next sections we explore different approaches as well as code examples of how to implement them.

What is considered as breaking Change?

Every change that has the potential to crash the consumer’s application is considered as breaking change. Deletion of an endpoint or a property from a response will most likely cause a crash or unwanted behavior in consumer’s side. Furthermore, a modification of an existing property key from a resource will also most likely cause deserialization problems leading to crashes.

More specifically the actions below are all considered breaking changes:

  • Delete a resource or endpoint.
  • Delete a property from a resource.
  • Delete a required parameter from the request.
  • Modifying an endpoint’s name.
  • Modifying a property key from a resource.
  • Modifying a required parameter from the request.
  • Modifying existing error response codes.
  • Modifying a validation of an existing resource.
  • Modifying an existing resource permission.
  • Adding a required parameter that wasn’t required or present before.
  • Adding a new validation to an existing request.

What isn’t a breaking change

Below we present various situations that are not considered as breaking changes. You can incorporate those changes in your API without worrying about crashing the consumer’s application.

  • Adding a new endpoint.
  • Adding a new property for an existing resource.
  • Adding new optional parameters in existing endpoints.
  • Adding new required parameters in existing endpoints but with a default value that ensures the same behavior as before the introduction of the new parameters.
  • Adding a new optional HTTP Header.

Various approaches to deal with breaking changes

In the following sections we show what options we can use in order to implement versioning in our API, as well as the advantages and disadvantages of each method.

URL version discriminator

Putting a discriminator on the URL you can separate multiple versions of your API. This can be part of the route URL or a query parameter. Both cases change the URL.

Examples
myapi.com/api/v2/properties
myapi.com/api/properties?api-version=v2
Advantages
  • Easy routing.
  • The URL contains all the information needed. That means it can be stored or shared without the need of any extra parameter/information.
  • Easier for the consumer to check the used version.
  • Easier usage tracking.
Disadvantages
  • You might end up using different resources for the same entity, which violates some of the REST principles.

Specify version on a predefined header

Using the Accept header in GET requests to declare the version of resource to retrieve and the Content-Type header for PUT and POST to declare the version of the content that you send. In case the client doesn’t specify the version you can specify clearly in your API documentation which version will be used.

With this approach you should define a media type for each version you have. For more information about the format of the Accept payload visit datatracker.ietf.org

Examples

We can define a media type “application/vnd.my-api.customer.v1” for version 1, and “application/vnd.my-api.customer.v2” for the version 2.

Advantages

  • Consumers can upgrade to the newer version without changing any of the endpoint URLs.

Disadvantages

  • The URL is not enough for knowing the version. Its harder to share URLs without communicating also some information about where to declare the desired version.
  • The consumer has to know the media types that exists.

Specify version on a custom header

Use a custom header to denote the version. For example you can use api-version header and a value that that suits you best.

Examples

In request headers add for version 1:

api-version:1.0

and for version 2

api-version:2.0

Advantages

  • Complete flexibility.
  • Orthogonal to the media type and URL. (media and URL can vary independently from version)
  • Like with the previous approach, consumers can upgrade to newer versions without changing the URLs.

Disadvantages

  • Similar to the previous approach the version header is another piece of information that must be communicated with the consumer.

Considerations to keep in mind

Regardless the approach, when the time of an upgrade is due, both old and new versions will coexist for a period of time.

This period should allow consumers to smoothly change their application to use your latest version. Also in case of putting a discriminator in the URL be sure to put this discriminator in all the rest routes (even when only a few endpoints have updated.). This don’t transfer the burden to consumers to remember which routes have which version numbers.

URL version discriminator in ASP.NET Web API

First, create a new Web API project in Visual Studio. Then create two folders inside Controllers folder named V1 and V2 respectively. In each folder create two controllers named CustomersController.cs

Next, import the Microsoft.Aspnetcore.Mvc.Versioning nuget package in order to handle versioning inside our application.

Import the Microsoft.AspNetCore.Mvc.Versioning nuget package.

In Startup.cs add support for versioning with AddApiVersioning extension method.

public void ConfigureServices(IServiceCollection services)
{
   services.AddControllers();
   services.AddApiVersioning();
}

Inside the controllers add a GET method in order to return some sample Customer items.

[Route("api/[controller]")]
[ApiController]
public class CustomersController : ControllerBase
{
   [HttpGet]
   public IEnumerable<Customer> Get()
   {
      return new List<Customer>() {
         new Customer()
         {
            FullName = "Erlich Bachman"
         },
         new Customer()
         {
            FullName = "Richard Hendricks"
         }
      };
   }
}

Similarly for V2 controller

[Route("api/[controller]")]
[ApiController]
public class CustomersController : ControllerBase
{
   [HttpGet]
   public IEnumerable<Customer> Get()
   {
      return new List<Customer>() {
         new Customer()
         {
            FirstName = "Erlich",
            LastName = "Bachman"
         },
         new Customer()
         {
            FirstName = "Richard",
            LastName = "Hendricks"
         }
      };
   }
}

Notice that in Version 2 we introduced a breaking change. We deleted a property in Customer model and introduced two new properties (We split the full name into firstName and lastName).

If we attempt to run and access the resource in URL /api/Customers we will get as response the following:

This is because we have not specified any default version yet.

Suppose that our first version is 1.0. We can add this as a default version when the consumer does not specify any. We modify the previous AddApiVersioning method in Startup.cs providing some configuration this time:

services.AddApiVersioning(config =>
{
   config.DefaultApiVersion = new ApiVersion(1, 0);
   config.AssumeDefaultVersionWhenUnspecified = true;
   config.ReportApiVersions = true;
});

When ReportApiVersions is true, the HTTP headers api-supported-versions and api-deprecated-versions will be added to all valid service routes. In addition, we modify the Route attribute in our controllers in order to parse the apiversion in the request URL api/{apiversion}/customers and also we add the ApiVersion attribute that denotes which version the controller supports.

For version 1, the CustomersController will be like this:

[ApiController]
[Route("api/v{version:apiVersion}/Customers")]
[ApiVersion("1.0")]
public class CustomersController : ControllerBase
{
   [HttpGet]
   public IEnumerable<Customer> Get()
   {
      // code omitted
   }
}

And for Version 2 CustomersController is like below:

[ApiController]
[Route("api/v{version:apiVersion}/Customers")]
[ApiVersion("2.0")]
public class CustomersController : ControllerBase
{
   [HttpGet]
   public IEnumerable<Customer> Get()
   {
      // code omitted
   }
}

Finally, lets examine some example requests using postman.

Making a request for version 1.0
Making a request for version 2.0

Versioning using headers and query parameters

In order to support versioning using headers like Accept or custom headers, we have to modify the api versioning configuration and add a few more entries.

services.AddApiVersioning(config =>
{
   config.DefaultApiVersion = new ApiVersion(1, 0);
   config.AssumeDefaultVersionWhenUnspecified = true;
   config.ReportApiVersions = true;
   config.ApiVersionReader = ApiVersionReader.Combine(
      new QueryStringApiVersionReader("api-version"),
      new HeaderApiVersionReader("api-version"),
      new MediaTypeApiVersionReader("ver"));
});

Using multiple entries for ApiVersionReader we can support multiple versioning schemas/approaches. Also the Route attribute in controllers should be clean.

[Route("api/[controller]")]

Query Parameter Versioning example

With the previous configuration we can append the api-version query parameter specifying the desired version as shown below.

Example Versioning request using a query parameter

Accept Header Versioning example

We can also use the Accept header providing a media type as the desired version

Example Versioning request using the Accept header

Custom Header Versioning example

Lastly, we use the custom api-version header to declare the desired version

Example Versioning request using a custom header

Resource/Recommendations

If you want to know more about versioning and deploying code in production I highly recommend reading the following book:

Also if you want more about versioning examples and in general be proficient at ASP.NET Web API you can read those books:

Write A Comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.