Tuesday, August 27, 2019

Backwards Compatibility: Techniques for HTTP APIs

Like most web developers, I do a lot of coordination between servers and clients. The most obvious interaction is between a customer's browser and my servers, but I also see a good amount of server-to-server communication.

Changes in distributed systems don't always get rolled out at the same time. Even inside a single "service", if you run multiple copies of that service for redundancy or high availability you have a distributed system and changes you make will not be applied simultaneously. If you offer an API, you don't have any control over when your clients upgrade their systems, and you probably don't want to break them.

I've found a few techniques for handling compatibility between systems during upgrades that I hope will give you some ideas for approaching the problem in your own work. Please don't consider them exhaustive - there are as many variations on this subject as there are systems. My work currently focuses on APIs, browsers, HTTP clients, and databases, so that's what I'm going to talk about.

Today's topic is HTTP APIs. Generally people want to consume them and get the any new features you make available: clients often have an interest in upgrading. So, you can often just worry about not breaking your clients without them knowing. With that in mind, lets explore how to make both non-breaking and breaking changes without causing your clients too much pain.

Non-breaking Changes 

Most of the time, you want to evolve your API without forcing your clients to implement your changes right away. These are non-breaking changes - your clients may upgrade to support them in the near future, but they won't stop working when you deploy the change.

Since we're talking about HTTP APIs, changes break down pretty easily to adding fields and adding or changing endpoints. This even holds true in non-RESTy environments, but frameworks like GraphQL probably give you an easier path to doing upgrades. I haven't used them, so I'm just commenting on REST-style environments. I'd love to hear how people handle this elsewhere!


Adding an endpoint

You've got one endpoint, say /users/{userId}. You want to add a /blogs path. That's completely safe - if no one's using it, you don't have to worry about breaking them! Clients can start using the new endpoint whenever it's convenient.

Adding a field 

Most JSON and XML parsers tolerate new fields well, or can be configured to do so. Unfortunately, you don't really get a say here - you have to define up front that clients need to tolerate unexpected fields and if you have one super-important client that can't, you'll just have to use other techniques. If they can,, though, you can add new fields as in the JSON example all you want and no one will break.

{
  "originalField": "value",
  "newField": "value",
  "addedAYearLater": "value"
}

However, you can't do this with lists. For example, you could define a Users endpoint as:

GET /users
[
  { "userId": 1, "username": "bob"},
  { "userId": 2, "username": "sally"}
]

...but then you'd only be able to add elements to the list or fields to the User objects themselves. It's often better for future work to return an object, and when you need to maybe add paging you don't need to break everyone to do it:

{
  "users": [
    { "userId": 1, "username": "bob"},
    { "userId": 2, "username": "sally"}
  ] ,
  "offset": 10
}

Breaking Changes

Sometimes you just have to change a system. Maybe you need a new naming scheme to better support new clients, or you want to change technologies. Either way, your clients will have to change. You don't have to force them to change in lockstep with you though. Here's a few ways to do it.

Changing a response type

Sometimes we get the structure of a response wrong. Returning a list and later needing an object as in "Adding a field" is a good example.

In my opinion, you should never change the structure of a given endpoint once published, like you you should never rebase a commit published to another Git repository. It really messes with your consumers and usually breaks them until they change their code to match your new output. Here's a few options you can do instead:


  1. Implement a new endpoint that returns the desired structure, and deprecate the original until the clients have had a chance to update
  2. (Variation on (1)) Version your endpoints, in the path (/v1/users, /v2/users) or perhaps using a header. I would recommend against using query parameters, because in the abstract you'd be mixing filters with response structure and that can be confusing. It may be an option if your clients are limited to paths and query parameters, though.
  3. If the data is identical but the client needs a different structure, you might use the Accept header for a content-negiotiating strategy. This is perfect for letting clients choose between formats like XML and JSON, but it's also great for clients that need some metadata on the response that, for others, would be just noise and bandwidth.

Renaming a field

Naming is hard, 'nuff said. You should approach this differently depending on if you control the clients or not.

If you control the clients, you can do a three-step upgrade.
  1. Add an identical field with your new, improved name. Do not remove the original yet.
  2. Upgrade all your clients to use the new name.
  3. Remove the original field once there's no one using it.
If you don't control the clients, I'd look really hard at why you need to change the name. You can deprecate the original and remove it after a reasonable amount of time, or...not, if people keep using it. It might be a better idea to leave the name alone and improve your documentation to compensate, and avoid the coordination.

You can also let a bunch of name changes stack up for a while, and deprecate and change them in a single batch. That would give your clients higher stability - for example, you might make changes on a 6-month schedule, adding the new names and deprecating the old in the first release and removing the old names in the next.


Wrapping up

Different frameworks and API styles have different ways of approaching compatibility - explore your tools and see which ways they can help you support changes without breaking your clients. For example, in the Java world Jersey abstracts out media types, which makes it really easy to support XML, JSON, or pretty much any structure you want. Maybe your framework lets you version endpoints without much effort, so you favor that strategy over relying on clients ignoring new fields.

Whatever you choose, think about what your clients need and evolve you API in a way that lets them evolve along with you.

Tuesday, October 28, 2014

Pressing a button


Take a blog. You create a post, but typically you don't publish it right away. (Unless you're me, impulsive and bad at proofreading.) You write a ton, edit it down to something your English teacher wouldn't spill red ink over, add some pictures and links, and then publish it. Typically you do that by pressing a single button, and often pressing that button takes you to the newly published article.

How might that translate into a RESTful client API? It starts off pretty simple - POST /articles to create, forwards to /articles/1. Edit a bit, then POST /articles/1 to update…all good so far. Now, how do I publish?

You could POST a full representation of the article, which might look like this:

{
   "text":"Ipsum dolerum",
   "published": false
}

That's annoying, because now the client has to know which property to set to publish a post.

You could use the PATCH verb, but that has the same problem as the "published" flag - the client has to know what property to set.

If you're really feeling purist today, you could use an explicit content-type with an empty body.

We could POST an application/json+articleStatus representation to /articles/1. This has some advantages, especially if the change has other properties. It's kind of heavy for a single property though.

Let's go over the principles of REST design:

  • Client–server - separation of concerns. That's exactly what we want. Clients shouldn't have to care about how the server publishes an article. Having to set a flag breaks that abstraction.
  • Stateless - everything needed to process the request is included in the request. Ok - the client should send an article id and something that shows the intention of publishing it.
  • Cacheable - no problem here, send me a no-cache header if you want.
  • Layered system - I don't care what system publishes my article.
  • Uniform interface - Now were getting somewhere!
    • Identification of resources - gotta have a url. Gotcha.
    • Manipulation of resources through these representations - Right, so the client should easily know how to publish a post.
    • Self-descriptive messages - yup, make sure the client can tell what to do.
    • Hypermedia as the engine of application state (HATEOAS) - Aha! Give back links for the client to use!




What conclusions can we draw when applying this to our API?

  1. Identify resources. We only have one - an article. That should always stand out. So, articles are the only things that have URLs. More specifically, /articles/1/publish is not a resource.
  2. If the client has a representation of an article, he should have everything he needs to publish it. That annoying "published" flag back there covers that pretty well, I guess…
  3. The client should be able to easily tell what he can do. Sorry, but that flag doesn't cut it here. Sure, he might figure it out in this case, but what if the article has more than one state, like "reviewing", "in moderation", and "published"? How would the client know how to put the resource into one of those states?
Our last option is query parameters. POST to /articles/1?publish. Now, a GET from /articles/1 will return:

{
   "metadata": {
      "publish": "/articles/1?publish",
      "save": "/articles/1"
   },
   "text": "Ipsum dolerum"
}

Now the server has fulfilled the uniform interface requirement. The article is clearly identified by the URI. The client now knows all the operations it can perform on the article. There are basic messages that describe what each operation does; these could be expanded further if necessary. And, HATEOAS is fulfilled since the server sends back the exact links the client needs to use to perform each operation.

Plus now the client doesn't have to care how an article is published. All he has to do is press a button.