This repository contains example Flask apps that are meant to visualize the difference between path- and header-based API versioning.
App available in through_header
package uses path-based API versioning. It
consists of two blueprints - api_v1
and api_v2
. Each one of them is
mounted under its own URL prefix. Effectively, both API versions are separate.
Being separate, both blueprints can use different databases and other resources (e.g. remote services) to serve API clients. It's also possible to easily extract one of them into completely new app.
Routing uses standard mechanisms (Blueprint.route
decorator) which means
that this scheme of API separation requires no changes to the underlying
framework.
Disabling one version of the API is as simple as commenting out two lines of
code (e.g. through_path/app.py
lines 12 and 13).
App available in through_header
package uses simple header-based API
versioning. It consists of two blueprints - api
. It's moounted under
/api
URL prefix. API version discovery is done by the blueprint's
before_request
callback. Both API versions share the same code base and
view functions must alter their behavior (e.g. response format) according to
API version being requested.
Extracting a single API version into a separate app requires going through the entire code base to move the relevant pieces of code into the new app.
Since both API versions share the same URL prefix, each view function must
define behavior for both API versions. If a given endpoint doesn't exist in a
given API version, then view function must properly react to this condition
(e.g. respond with HTTP 400
status).
Routing uses standard mechanisms (Blueprint.route
decorator) which means
that this scheme of API separation requires no changes to the underlying
framework.
Disabling one version of the API at least requires changes in the
before_request
filter.
Suppose you have an app that exports some sort of API, e.g. for mobile clients and you wish to introduce a new version of the API. Obviously, you're going to need keep the old version around. You decide to go with different clusters for both the API versions.
NOTE: I only have limited experience with nginx. Somebody smarter than me can surely do it better :).
Let's look at nginx config for path-based versioning:
server {
server_name example.com;
listen 127.0.0.1:80;
location /api/v1 {
proxy_pass http://api1.example.com:8080;
}
location /api/v2 {
proxy_pass http://api2.example.com:8080;
}
location / {
proxy_pass http://web.example.com:8080;
}
}
Here, routing between servers is done via standard location
directive. Easy
to maintain, easy to verify.
Now, how about some config for header-based versioning:
server {
server_name api_through_header.web;
listen 127.0.0.1:80;
location /api {
if ($http_x_apiversion = 1) {
proxy_pass http://api1.example.com:8080;
break;
}
if ($http_x_apiversion = 2) {
proxy_pass http://api2.example.com:8080;
break;
}
return 400;
}
location / {
proxy_pass http://web.example.com:8080;
}
}
IMO routing here is tricky. Personnaly, I like to keep my nginx/haproxy/Varnish configs as simple as possible to avoid hunting weird bugs in production when something goes wrong.
As I stated on Twitter, I don't believe in header-based API versioning. It
forces me to hack and write ugly code. Not to mention headers similar to
Accept: application/vnd.me.v1+json
. Path-based versioning, on the other hand,
uses standard HTTP and framework mechanisms to achieve versioning.