-
-
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>`_ | |
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.
+1