On versioning WebAPI REST services

When you develop REST services you are going to run into the little matter of versioning, there is just no way to avoid it if you want to make any change to your REST service. Now there are lots of different ways to go about this and these different ways have their pro’s and con’s. What your version strategy is also depends on if your REST services are read-only or read-write with read-only services being somewhat simpler due to their smaller API surface.

In these examples I am using the ASP.NET WebAPI but these same principles apply to REST services however you develop them.

 

Adding properties to a read-only service

This is by far the easiest case to handle. Because you are only adding new properties and not changing any of the existing ones you don’t really need to worry about versioning. After all existing client applications only check the properties they know about and all of them are still there. These client applications can be upgraded to use the new properties whenever you want. Nice and simple,, if only life was always that simple :-)

Of course this are already more complicated when the REST service is read-write. In that case old client’s will most likely not send the new data members of our V2 resource. A smart client might just round trip all unknown data but you can’t really rely on that. As a result you either have to accept that a PUT request from an older client drops data being added by a new client application or you have to start implementing merge like functionality. But that really should be done using the PATCH HTTP method and not a PUT where the client expects the resource to be replaced with whatever is send and not merged with additional data.

 

Using the URL to version services

One approach that is used to version services is to include a version identifier in the URL. The benefit of this approach is that it is relatively simple to implement. However there is a drawback in that a small versioning change to just one resource means we have to create lots of new routes and possibly a set of new controllers.

To illustrate the problem I am going to use a Person class with a relative small versioning issue, in V1 we are using a FullName property and in V2 these are split into FirstName and LastName.

   1: namespace VersioningRest.Models.V1


   2: {


   3:     public class Person


   4:     {


   5:         public int Id { get; set; }


   6:         public string FullName { get; set; }


   7:     }


   8: }


   9:  


  10: namespace VersioningRest.Models.V2


  11: {


  12:     public class Person


  13:     {


  14:         public int Id { get; set; }


  15:         public string FirstName { get; set; }


  16:         public string LastName { get; set; }


  17:     }


  18: }

As you can see the change is minimal but this still requires two controllers, probably with almost identical code and routing for both versions of the resource like this:

   1: config.Routes.MapHttpRoute(


   2:     name: "DefaultV1Api",


   3:     routeTemplate: "api/v1/people/{id}",


   4:     defaults: new


   5:     {


   6:         controller = "PeopleV1",


   7:         id = RouteParameter.Optional


   8:     }


   9: );


  10:  


  11: config.Routes.MapHttpRoute(


  12:     name: "DefaultV2Api",


  13:     routeTemplate: "api/v2/people/{id}",


  14:     defaults: new


  15:     {


  16:         controller = "PeopleV2",


  17:         id = RouteParameter.Optional


  18:     }


  19: );

Using this we can do two requests to get specific versions as seen below

image image

 

Simple as this may be it quickly becomes very tedious with lots of code duplication so it isn’t really recommended. That said it is a relatively common and popular way of versioning REST services.

 

Using a custom HTTP header

Another approach taken is using custom HTTP headers. An example of this can be found in the Restify documentation where they use the custom HTTP Accept-Version header for Versioned Routes. While this may seem a good idea, after all we are leveraging HTTP by adding a custom HTTP header this is not quite as useful as you might expect. The following is a some example code on how you might do this.

   1: public class PeopleController : ApiController


   2: {


   3:     //GET api/<controller>


   4:     public HttpResponseMessage Get()


   5:     {


   6:         var version = DetermineApiVersion();


   7:  


   8:         object person;


   9:         switch (version)


  10:         {


  11:             case 1:


  12:  


  13:                 person = new[] { new V1Models.Person { Id = 1, FullName = "Maurice de Beijer" } };


  14:                 break;


  15:  


  16:             default:


  17:                 person = new[] { new V2Models.Person { Id = 1, FirstName = "Maurice", LastName = "de Beijer" } };


  18:                 break;


  19:         }


  20:  


  21:         var response = Request.CreateResponse(person);


  22:         response.Headers.CacheControl = new CacheControlHeaderValue


  23:         {


  24:             MaxAge = TimeSpan.FromSeconds(15)


  25:         };


  26:         response.Headers.Vary.Add("Accept-Version");


  27:         return response;


  28:     }


  29:  


  30:     private int DetermineApiVersion()


  31:     {


  32:         var version = int.MaxValue;


  33:         IEnumerable<string> acceptVersion;


  34:         if (Request.Headers.TryGetValues("Accept-Version", out acceptVersion))


  35:         {


  36:             foreach (var item in acceptVersion)


  37:             {


  38:                 version = int.Parse(item);


  39:             }


  40:         }


  41:         return version;


  42:     }


  43: }

 

So what is the problem with this approach?

While this approach works it means we have to do the versioning inside of our API Controller. This may not be a big problem but it means we have to add another responsibility to the controller which it should really not have to worry about. As a result I am not a huge fan of this even though it works perfectly well. One thing you need to be aware of if that HTTP caching might be a problem, the same URL can return different responses. So as a result you should always add "Accept-Version" to the HTTP Vary header. This value is used to determine if a cached resource can be used or should be seen as something different.

 

Using HTTP content negotiation using the Accept HTTP header

While the previous approach seems nice enough as we are leveraging the HTTP infrastructure it turns out we can just use an existing piece. We are using the HTTP Accept header and content negotiation all the time and we can just hook onto this system.

With this system we just have a simple controller returning the last version of the resource like this:

   1: public class PeopleController : ApiController


   2: {


   3:     //GET api/<controller>


   4:     public HttpResponseMessage Get()


   5:     {


   6:         var people = new[] { new Person 


   7:                 { 


   8:                     Id = 1, 


   9:                     FirstName = "Maurice", 


  10:                     LastName = "de Beijer" 


  11:                 }};


  12:  


  13:         var response = Request.CreateResponse(people);


  14:         response.Headers.CacheControl = new CacheControlHeaderValue


  15:         {


  16:             MaxAge = TimeSpan.FromSeconds(15)


  17:            


  18:         };


  19:         response.Headers.Vary.Add("Accept");


  20:         return response;


  21:     }


  22: }

 

And we use different mime types in the HTTP Accept header to specify the resource version to use. In this case I am using "application/vnd.person.v1+json" or "application/vnd.person.v2+json". The vnd basically means we are using a vendor specific type. The v1 or v2 indicates the version and the +json means we want a JSON representation. This last part could be replaced with +xml for an XML representation etc.

With this scheme we use MediaTypeFormatters to return the proper JSON data for a specific version. The V2 version is simple, after all it just returns the person resource as JSON data. The "application/vnd.person.v2+json" formatter converts the V2 version into a V2 version person and writes that as JSON to the response steam like this:

   1: public class PeopleV1MediaTypeFormatter : BufferedMediaTypeFormatter


   2:  {


   3:      public PeopleV1MediaTypeFormatter()


   4:      {


   5:          SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.person.v1+json"));


   6:      }


   7:  


   8:      public override bool CanReadType(Type type)


   9:      {


  10:          return false;


  11:      }


  12:  


  13:      public override bool CanWriteType(Type type)


  14:      {


  15:          return typeof(IEnumerable<V2.Person>).IsAssignableFrom(type);


  16:      }


  17:  


  18:      public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)


  19:      {


  20:          var peopleV2 = value as IEnumerable<V2.Person>;


  21:          if (peopleV2 != null)


  22:          {


  23:              var peopleV1 = peopleV2.Select(p => new V1.Person


  24:              {


  25:                  Id = p.Id,


  26:                  FullName = p.FirstName + " " + p.LastName


  27:              });


  28:  


  29:              using (var writer = new StreamWriter(writeStream))


  30:              {


  31:                  var serializer = new JsonSerializer();


  32:                  serializer.Serialize(writer, peopleV1);


  33:              }


  34:          }


  35:      }


  36:  }



 



This approach is simple enough and uses standard HTTP semantics so should work just fine. Just make sure to include the Vary HTTP header with a value of Accept like in the API controller above.











 



Conclusion



While I think using the HTTP Accept header and content negotiation is the best option it seems that changing the URL to contain the version is the most popular. Either works and changing the URL is certainly a very easy to understand way of achieving the versioning goal.



 



Enjoy!

2 thoughts on “On versioning WebAPI REST services

  1. I personally like the url changing option, because it is much more easy to understand and therefore easier to consume.
    Also it feels more ‘RESTful’, but thats a bit hard to explain :-)

    Ronald

  2. @Ronald,

    Using the URL is certainly the easiest to see. However I would argue it is less RESTfull not more. The version of the service is not part of the resource itself, just how you interact with it. Just as is the case with content negotiation and we don’t normally include the content type in the URL either.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>