Breaking Changes Bad! API Versioning Good!
As anyone who has built or regularly uses an API realizes sooner or later, breaking changes are very bad and can be a very serious blemish on an otherwise useful API. A breaking change is a change to the behavior of an API that can break a user’s integration and result in a lot of frustration and loss of trust between the API provider and user. Breaking changes require that users be notified in advance (with accompanying mea culpas) rather than a change that just shows up, such as a delightful new feature. The way to avoid that frustration is to version an API with assurances from the API owner that there will be no surprising changes introduced within any single version.
So how hard can it be to version an API? The truth is it’s not, but what is hard is maintaining some sanity by not needlessly devolving into a dizzying number of versions and subversions applied across dozens of API endpoints with unclear compatibilities.
We introduced v1 of the API three years ago and did not realize that it would be going strong to this day. So how have we continued to provide the best email delivery API for over two years but still maintain the same API version? While there are many different opinions on how to version REST APIs, I hope that the story of our humble yet powerful v1 might guide you on your way to API versioning enlightenment.
REST Is Best
The SparkPost API originates from when we were Message Systems, before our adventures in the cloud. At the time we were busy making final preparations for the beta launch of Momentum 4. This was a major upgrade to version 3.x, our market leading on-premise MTA. Momentum 4 included an entirely new UI, real-time analytics, and most importantly a new web API for message injection and generation, managing templates, and getting email metrics. Our vision was of an API first architecture – where even the UI would interact with API endpoints.
One of the earliest and best decisions we made was to adopt a RESTful style. Since the late 2000s representational state transfer (REST) based web APIs are the de-facto standard of cloud APIs. Using HTTP and JSON makes it easy for developers, regardless of which programming language they use – PHP, Ruby, and Java – to integrate with our API without knowing or caring about our underlying technology.
Choosing to use the RESTful architecture was easy. Choosing a versioning convention was not so easy. Initially we punted on the question of versioning by not versioning the beta at all. However, within a couple months the beta was in the hands of a few customers and we began building out our cloud service. Time to version. We evaluated two versioning conventions. The first was to put the versioning directly in the URI and the second was to use an Accept header. The first option is more explicit and less complicated, which is easier for developers. Since we love developers, it was the logical choice.
API Governance
With a versioning convention selected we had more questions. When would we bump the version? What is a breaking change? Would we reversion the whole API or just certain endpoints? At SparkPost, we have multiple teams working on different parts of our API. Within those teams, people work on different endpoints at different times. Therefore, it’s very important that our API is consistent in the use of conventions. This was bigger than versioning.
We established a governance group including engineers representing each team, a member of the Product Management team, and our CTO. This group is responsible for establishing, documenting, and enforcing our API conventions across all teams. An API governance Slack channel also comes in handy for lively debates on the topic.
The governance group identified a number of ways changes can be introduced to the API that are beneficial to the user and do not constitute a breaking change. These include:
- A new resource or API endpoint
- A new optional parameter
- A change to a non-public API endpoint
- A new optional key in the JSON POST body
- A new key returned in the JSON response body
Conversely, a breaking change included anything that could break a user’s integration such as:
- A new required parameter
- A new required key in POST bodies
- Removal of an existing endpoint
- Removal of an existing endpoint request method
- A materially different internal behavior of an API call – such as a change to the default behavior.
The Big 1.0
As we documented and discussed these conventions, we also came to the conclusion that it was in everyone’s (including ours!) best interest to avoid making breaking changes to the API since managing multiple versions adds quite a bit of overhead. We decided that there were a few things we should fix with our API before committing to “v1”.
Sending a simple email required way too much effort. To “keep the simple things simple” we updated the POST body to ensure that both simple and complex use cases are accommodated. The new format was more future-proof as well. Secondly we addressed a problem with the Metrics endpoint. This endpoint used a “group_by” parameter that would change the format of the GET response body such that the first key would be the value of the group by parameter. That did not seem very RESTful so we broke each group by into a separate endpoint. Finally we audited each endpoint and made minor changes here and there to ensure they conformed with the standards.
Accurate Documentation
It is important to have accurate and usable API documentation to avoid breaking changes, of the deliberate or unintentional kind. We decided to use a simple API documentation approach leveraging a Markdown language called API Blueprint and manage our docs in Github. Our community contributes and improves upon these open source docs. We also maintain a nonpublic set of docs in Github for internal APIs and endpoints.
Initially, we published our docs to Apiary, a great tool for prototyping and publishing API docs. However, embedding Apiary into our website doesn’t work on mobile devices so we now use Jekyll to generate static docs instead. Our latest SparkPost API docs now load quickly and work well on mobile devices which is important for developers who are not always sitting at their computer.
Separating Deployment from Release
We learned early on the valuable trick of separating a deployment from a release. This way it’s possible to frequently deploy changes when they are ready through continuous delivery and deployment but we don’t always publicly announce or document them at the same time. It’s not uncommon for us to deploy a new API endpoint or an enhancement to an existing API endpoint and use it from within the UI or with internal tools before we publicly document it and support it. That way we can make some tweaks to it for usability or conformance to standards without worrying about making a dreaded breaking change. Once we are happy with the change we add it to our public documentation.
Doh!
It is only fair to admit that there have been times where we have not lived up to our “no breaking changes” ideals and these are worth learning from. On one occasion we decided it would be better for users if a certain property defaulted to true instead of false. After we deployed the change we received several complaints from users since the behavior had changed unexpectedly. We reverted the change and added an account level setting – a much more user friendly approach for sure.
Occasionally we are tempted to introduce breaking changes as the result of bug fixes. However, we decided to leave these idiosyncrasies alone rather than risk breaking customer’s integrations for the sake of consistency.
There are rare cases where we made the serious decision to make a breaking change – such as deprecating an API resource or method – in the interest of the greater user community and only after confirming that there is little to no impact to users. For example, we deliberately made the choice to alter the response behavior of the Suppression API but only after carefully weighing the benefits and impacts to the community and carefully communicating the change to our users. However, we would never introduce a change that has a remote possibility of directly impacting the sending of a user’s production email.
Introducing v2 – Not Yet!
Who knows how long v1 of the SparkPost API will continue to reign. It’s certainly outlived my own expectations. What will be the driving reason to release v2 of the API? I would love to hear your thoughts on this topic. If you have any questions about our API versioning or would like advice on how best to solve your own API versioning challenges please don’t hesitate to connect on Twitter. And if you like building awesome APIs you are in luck because we are always hiring. To learn more about how we built our API check out How SparkPost Built the Best Email API for Developers.
This post was originally published on the SparkPost blog.
Top comments (9)
Really interesting read, thanks for posting. This is something creeping up my to-do list for me to start defining as I get closer to the point of me needing to declare something as version 1 of my API so came at a good time for me!
I wish I could agree, but I don't. With API versioning, you're only postponing or ignoring the problems you're going to have to face down the line anyway. As with most problems that are eventually going to hit you like a bus, you should tackle them as early as possible before the bus gains momentum and finally kills you. Ok, an API with a version number likely won't kill you, but please bear with me. :-)
When you have 4 different versions of your API available, each with something like 20 individual resources, that's 80 different resources you need to support. If you share codebase between all of these different versions, how can you guarantee that a feature added in version 4 won't break something in version 1? If you split the codebase between the 4 versions, you have four times as much work on your hands in terms of maintenance, bug fixing, etc. If each version is independently deployed, documented, etc., that's also a lot of overhead and duplicated effort. Can your company really sustain that in the long run? Is it worth it?
Versioning your API doesn't deal with having a plan for how to deprecate, rename and remove things either. Adding a new version of your API does not automatically remove old ones. They are there because you need to support the features they provide, at the precise URLs they all live, forever and ever. Also, versioning the resources in your API is way more coarse grained than necessary. If you have 20 resources and remove one property in one of them, why should the 19 untouched resources also have their version number incremented?
Instead of a version strategy, you should have a change strategy. That means you should plan how to introduce new features and how to introduce breaking changes. You should figure out the answers to questions like: Who needs to be notified when something changes? How are you going to notify them? If your plan to deprecate something turns out difficult to a customer, can you postpone the removal of that feature?
These are questions you will have to answer even if you have version numbers in your APIs, it's only that you will get them at a much later stage when it's much harder to deal with. Deal with them upfront and it's going to be much easier, because you can design everything around being able to simply answer these questions.
If you want to remove a feature (say a property from a resource), you can for instance start by removing it from the API documentation and notify existing customers of its deprecation via e-mail or on a dashboard they view often. Then you can monitor the usage of said feature and notify customers who still use it. If they still don't update their clients, you can offer them money to do it. That's money you're going to save by not maintaining the old version, so it's a win for both parties.
As outlined by Zdenek Nemec in API Change Management, the rules for extending your API is simply:
And you should version things, just not the resources themselves. Here's what you should version:
So, how does this work in practice, then? Through content negotiation, and most importantly; hypermedia. If this has you intrigued, please spend 20 minutes watching my presentation on hypermedia to get an idea of what it is and how you can use it to create more flexible APIs without the need of version numbers.
In general I agree with you. Perhaps my point was lost. Versionless API is what I recommend, with a strategy for managing change. You might find my QCon talk on this subject interesting - there is a recording. sparkpost.com/blog/microservices-b...
Nice comment to this article. Sticking to version 1, as the final version of our API, it the most convenient approach. In my opinion it extremely hard to achieve this in real life scenarios and we should keep in mind that in the future we'll be pushed to release new version of our API to meet customers requirements.
Nice article which show new point of view on API versioning. Unfortunately in my career I've had always to introduce new version of API to introduce new features for my clients and I end with multiple versions of API which should be maintained.
Thanks for reading, Rafal. "Versionless" APIs are definitely becoming more popular, for good reason. Hopefully, you will get a chance to adopt this approach in the future. You may find my QConSF talk on the subject interesting. sparkpost.com/blog/microservices-b...
In green field projects I'm going to use "versionless" API but I need to maintain existing one which weren't designed to achieve this.
I'm sure that I'll look at it. Thanks for your replay.
for Node.js / ExpressJS readers - github.com/lirantal/express-versio...
would be happy to get feedback :)