Skip to content

Instantly share code, notes, and snippets.

@steveklabnik
Created June 18, 2013 01:06
Show Gist options
  • Save steveklabnik/5801884 to your computer and use it in GitHub Desktop.
Save steveklabnik/5801884 to your computer and use it in GitHub Desktop.
Versioning is an anti-pattern
=============================
We often say "use the right tool for the job", but when managing change
in software systems, we always use versioning. Hypermedia APIs are
actually hindered by introducing versioning and manage change in a
different way. With that in mind, there are also a lot of options for
managing change in a Hypermedia API. We'd like to change our service and
break as few clients as possible. Versioning is only one way to manage
change, though... and my contention is that it's not appropriate for
hypermedia services.
REST limits the possible contract changes to just two kinds: adding
data or controls and removing data or controls from a message
Jan Algermissen
How and Why Software Changes
----------------------------
There's a really great book about changing software titled `"Working
Effectively with Legacy
Code" <http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052>`_.
In it, Michael Feathers talks about how working with legacy code really
means managing change. From the book:
For simplicity's sake, let's look at four primary reasons to change
software.
1. Adding a feature
2. Fixing a bug
3. Improving the design
4. Optimizing resource usage
and
In general, three different things can change when we do work in a
system: structure, functionality, and resource usage.
I consider 'removing a feature' to be a subset of 'fixing a bug.' You're
taking some unwanted behavior out of a system.
There's another good model for thinking about changes: the
backwards/forwards compatibility model. Something is considered
'backwards compatible' if it is able to work with older versions of the
same software. Something is considered 'forwards compatible' if it can
work with newer versions of the same software. Ultimately, we want to
shoot for designs that are both forwards and backwards compatible.
We can combine both of these axes of change. We may add a feature and
make it backwards compatible when doing so. We may fix a bug and make it
forwards compatible. One model is about the *kind* of change, the other
is about the *compatibility* that the change has with our system.
Versioning is Coupling
----------------------
The main problem with versioning is that it encourages tight coupling
between clients and servers. Before I justify that opinion, let's
consider why coupling between clients and servers is bad.
From Fielding:
Perhaps most significant to the Web, however, is that the separation
[of clients from servers] allows the components to evolve
independently, thus supporting the Internet-scale requirement of
multiple organizational domains.
And again:
[Via the uniform interface] implementations are decoupled from the
services they provide, which encourages independent evolvability.
Basically, coupling between clients and servers means that it's hard to
scale. When changes need to be made, you have to notify clients to
upgrade. Imagine if your web browser needed to be updated every time
Google deployed. Granted, Chrome feels like that sometimes, but you get
my drift. ;) In fact, Chrome auto-updates so often and silently
*because* getting clients to upgrade is such a problem.
Versioning as coupling should be obvious: you want a particular client
version with a particular server version. This is normally considered a
good thing, though: by introducing this coupling, you protect yourself
against future breakage. Basically, versioning allows us to be really
reckless with our changes. We can totally re-write things between
version 1 and version 2. The cost we pay is that coordination cost of
getting all clients and servers updated to the newest version as quickly
as possible. However, if we took a more conservative and careful
approach to change, we could do a little bit of extra work and gain a
lot of benefits.
Managing change without versioning
----------------------------------
So what do we actually do, then? Let's examine the two types of change:
forwards compatibility and backwards compatibility.
Forwards compatibility
~~~~~~~~~~~~~~~~~~~~~~
There's one primary consideration with designing clients for forwards
compatible changes: ignore what you don't understand. That way, new
functionality won't break you.
To build on the RESTbucks example (used in the book `Rest in
Practice <http://www.amazon.com/REST-Practice-Hypermedia-Systems-Architecture/dp/0596805829/>`_
as well as the article `How to GET a Cup of
Coffee <http://www.infoq.com/articles/webber-rest-workflow>`_ by the
same authors), let's examine this response for paying for a cup of
coffee:
::
<order>
<drink>latte</drink>
<cost>3.00</cost>
<link rel="payment" uri="...">
</order>
Let's add some functionality to use a gift card to get a discount. The
response would look like this:
::
<order>
<drink>latte</drink>
<cost>3.00</cost>
<link rel="payment" uri="...">
<link rel="giftcard" uri="...">
</order>
If an old client is correctly written, it shouldn't break when it sees
this response. It should do the exact same thing as the first response.
It just ignores the new functionality.
You can also make your media type amenable to making old clients pick up
new changes. For example, Collection+JSON has a queries section that
looks like this:
::
"queries" : [
{"rel" : "search", "href" : "http://example.org/friends/search",
"prompt" : "Search"
"data" : [
{"name" : "search", "value" : ""}
]
}
]
Let's say that we used to have this search response. We want to remove
it, and add a new query, filter. The response looks like this:
::
"queries" : [
{"rel" : "filter", "href" : "http://example.org/friends/filter",
"prompt" : "Filter"
"data" : [
{"name" : "filter", "value" : ""}
]
}
]
You can imagine that now, since our client was coded against the ideas
of 'queries' rather than one of search directly, it'd be pretty trivial
for the client to not display a search box and display a filter box
instead when it gets this new response.
Backwards compatibility
~~~~~~~~~~~~~~~~~~~~~~~
From time to time, we want to remove functionality that used to exist.
We may want to make our old process 'deprecated', and so we tell new
clients to ignore it if it's there, or to prefer some sort of affordance
before using an older one.
This can be handled in the opposite way we dealt with new functionality:
if you don't see something, don't display it. So when we drop the Search
functionality, even if we can't find the new Filter stuff or display it,
we shouldn't be displaying a search box. The client isn't broken
directly, as our business process is no longer valid. It can't make
invalid requests.
Considerations for Media Type Design
------------------------------------
If you're making a new media type, consider *very* carefully if
something needs to be REQUIRED for your design. Removing these elements
later will force you to create a new explicit version or support it as
deprecated for the rest of time. Tread carefully!
While many people discuss versioning within the context of 'version the
URI or version the media type', they often forget another option:
version within the message. HTML does this, for example, with its
DOCTYPEs. This is how ``text/html`` as a media type has been unversioned
for almost three decades. However, note that going forward,
``<!DOCTYPE html>`` is the only doctype, and HTML will no longer be
versioned. Everything will be fully backwards-compatible as well as
forwards-compatible forever.
Sources Cited
-------------
- `"Understanding the role of media types in RESTful applications", Jan
Algermissen <http://www.slideshare.net/algermissen/understanding-the-role-of-media-types-in-restful-applications>`_
- `"Working Effectively With Legacy Code", Michael C.
Feathers <http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052>`_
- `Forward
Compatability <http://en.wikipedia.org/wiki/Forward_compatibility>`_
- `Backward
Compatability <http://en.wikipedia.org/wiki/Backward_compatibility>`_
- `"Architectural Styles and the Design of Network-based Software
Architectures, Section 5.1.2" Roy
Fielding <http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_2>`_
- `"Architectural Styles and the Design of Network-based Software
Architectures, Section 5.1.5" Roy
Fielding <http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5>`_
- `"How to GET a Cup of
Coffee" <http://www.infoq.com/articles/webber-rest-workflow>`_
- `"HTML is the new
HTML5" <http://blog.whatwg.org/html-is-the-new-html5>`_
@mamund
Copy link

mamund commented Jun 20, 2013

+1

@kevinswiber
Copy link

This is good stuff.

However...

We put too much emphasis on "versioning as an anti-pattern" and ignore real-world events that are significant. Just about everything you (@steveklabnik) suggest in this gist is great in many cases, but not all.

Sometimes breaking backward compatibility is the right choice. Technical specifications don't always determine that. Sometimes it's the business development team or the federal government.

"If you don't see something, don't display it," doesn't work for automated clients. Automated clients have a much tighter coupling to the representation and pre-defined workflow. Introducing a new link relation to handle it only works if the client is upgraded. When the old link relation must be retired, those clients will break.

There is a big difference between media type versioning, protocol versioning, API versioning, and domain versioning.

When the domain needs to drastically change, the APIs surrounding that domain are impacted. When optional changes are introduced, that's no big deal. When required changes are introduced, automated clients stop working.

In an ideal world, this wouldn't be an issue. I've spent much of my software development career learning to effectively manage the implementation of extremely complex business logic in big software systems. The cost of breaking backward compatibility can be significant, but sometimes the cost of maintaining it is much higher or not even an option. Ignoring that is to ignore the problems faced by many developers today, and I believe it negatively impacts the adoption of hypermedia.

I'd love to see more popular hypermedia spokespeople taking this stance. Versioning is a reality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment