CodeSnips

Wednesday, September 18, 2013

Versioning REST web services in WebApi

Much has been written on the subject of the proper approach to versioning REST web services  If you google long enough, you find there are two basic approaches:

1. Put the version identifier on the URI somehow:
    http://myhost/Account
    http://myhost/v2/Account
    http://myhost/Account/?version=2.0
    http://myhostV2/Account

2. Use the HTTP headers: 'Accept', 'Content-Type', and/or 'X-mycustom' to pass the desired version from the client.
    Accept: application/myhost-v2+js
    Content-Type: application/myhost-v2+js
    X-Version: 2.0


Any scheme which requires a URI change seems like a bad idea to me. The URI is supposed to be, essentially, a coordinate to a specific entity. Adding version to the coordinate will break older clients immediately regardless of whether the entity has changed in a breaking way.

A more robust approach would allow clients and the web service to negotiate the version needed, but leave the URI the same. This is more compatible with the semantics of HTTP.

Specifically in the case where we are using Microsoft's WebApi to provide REST web services, I wanted to see if I could add version handling in the least obtrusive way for programmers, and to that end I wanted something that fit the following design goals:

Design Goals
1. The URI doesn't change.
2. Handling the logic to shape the entity based on version should be done within a single method (or class.)
3. I want to minimize any special handling in the ApiController.
4. I want to decouple my domain entities from my model entities.
5. I want to have zero configuration changes to make in IIS or my web.config.

I think the following solution meets each of the above goals.

Example Code

To avoid creating new content types, which would require me to alter my IIS config [goal 5], I've chosen to use a custom header: "X-Version". This allows me to leave my URI format alone [goal 1]

To minimize any special handling of this header in my ApiControllers [goal 3], I've created a base class from which my controllers will inherit. It sets a "Version" property on the controllers which inherit from this base class:

    public class BaseApiController : ApiController
    {
        public double Version { get; set; }

        protected override void Initialize(HttpControllerContext controllerContext)
        {
             base.Initialize(controllerContext);
             Version = 1.0;
             var versionHeader = 
                 Request.Headers.FirstOrDefault(h => h.Key == "X-Version");
             if (versionHeader.Value != null)
             {
                 double version;
                 if (double.TryParse(versionHeader.Value.ToList()[0],out version))
                 {
                     Version = version;
                 }
             }
        }
    }

    // Example usage
    public class AccountController : BaseApiController
    {
        IAccountRepo _accountRepo;

        public AccountController(IAccountRepo accountRepo)
        {
            _accountRepo = accountRepo;
        }

        public IEnumerable Get()
        {
            foreach (var acct in _accountRepo.getAll())
            {
                yield return acct.AccountMap(Version);
            }
        }

        public Account Get(int id)
        {
            return _accountRepo.getById(id).AccountMap(Version);
        }

        // We handle the other verbs (POST, PUT, etc.) similarly
        // with a mapping from Model to Domain.
   }



I want to send back a Version property on all my model entities so my client can confirm it is getting the right version of the model entities it requested.

    public abstract class ModelBase
    {
        public double Version { get; set; }
    }

    public class Account : ModelBase
    {
        public int Id { get; set; }
        public string AccountCode { get; set; }
        public string Name { get; set; }
        public string AccountName { get; set; }
        public bool IsActive { get; set; }
    }



To decouple my domain entities (in this case Account) [goal 4] from my model entities, I've chosen to implement a "mapper" as an extension method on my domain entity type. This extension method takes a version parameter so it can choose how to return the data. The mapper also updates the Version property. All my mapping logic goes into this method [goal 2] so I only have to make changes here if I create a new version of the model entity.

        

        // Note: this is the mapper from Domain to Model entity.
        // Same idea for the Model to Domain mapper would apply (not shown here)
        //
        public static Model.Account AccountMap(this Domain.Account acct,
                                               double version)
        {
            if (acct == null)
                return null;

            // Version 1 Account Model

            if (version == 1.0)
            {
                return new Account
                {
                    Version = version,
                    Id = acct.Id,
                    AccountCode = acct.AccountCode,
                    Name = acct.Name,
                    IsActive = acct.IsActive,
                    AccountName = null,               // not used in v1
                };
            }

            // Version 2 (Current) Account Model
            return new Account
            {
                Version = version,
                Id = acct.Id,
                AccountCode = "V2" + acct.AccountCode, // content
                Name = null,                           // deprecated
                IsActive = acct.IsActive,
                AccountName = acct.Name,               // new

            };
        }
    }



Version mapping. There are basically four kinds of changes: adding properties, renaming properties, deprecating properties, and changing the content of existing properties.

One drawback of the serialization of the C# model classes is they can't be dynamic (at least without a bunch of ugly work), so to avoid breaking changes, you have to leave old properties in place on the model classes. However, this isn't too bad and you're able to handle the various kinds of changes noted above and not break clients expecting the old versions. New version clients will not be cluttered with old properties either - so most of this mapping mess goes into a single set of mappers in the server code.

Summary

The work in this example creates a couple of small base classes and a version-aware mapping function. The base classes could easily be added to a common library for other applications to use.  The mapping function is an example of one way to transform models based on version - there are undoubtedly better approaches, but the end result is you can manage version changes to models in one place.

I've learned that to handle versioning in a clean way, it's important to design ahead of time and make sure allowances are made for future change in both client and server code. This particular approach meets the goals I set out.

Lastly, non-WebApi REST web service frameworks that are implemented in more dynamic, loosely typed, languages would have a bit easier time transforming the returned data based on version. In my case, I wanted to use as much of C# and WebApi out-of-the-box as possible (I guess this is the implied design "goal 6"), but I think any solution should probably address the first 5 goals above.

No comments:

Post a Comment