Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., @awscloud, former CTO @artsy, +@vestris, NYC

Email Twitter LinkedIn Github Strava
Creative Commons License

I’ve seen two common API versioning strategies in the wild. The first is to use a single API version and gradually deprecate methods. This usually means introducing new API routes, while retiring old ones, and representing the same objects in multiple, versioned, formats. Fast forward a few years and you are likely to inherit a significant amount of technical debt. The second strategy involves making a clean cut, leaving the version one of the API alone and building a fresh, new, API version two.

Starting with the Grape 0.6.0 you can have a third alternative: building a new API version incrementally on top of a previous one. There’re no hacks involved. Consider the following trivial API.

module Acme
 class V1 < Grape::API
   format :json
   version 'v1', using: :header, vendor: 'acme', format: :json

   desc "Returns the current API version, v1."
   get do
     { version: 'v1' }
   end

   desc "Returns pong."
   get "ping" do
     { ping: "pong" }
   end
 end
end

Define the next API version.

module Acme
 class V2 < Grape::API
   format :json
   version 'v2', using: :header, vendor: 'acme', format: :json

   desc "Returns the current API version, v2."
   get do
     { version: 'v2' }
   end
 end
end

At this point we want v1 to be identical to v2, except for the root method. We’ll start by allowing v1 to respond to both v1 and v2 requests.

version ['v2', 'v1'], using: :header, vendor: 'acme', format: :json

Mount v2 before v1. The default versioning behavior is to cascade the request to the next Rack middleware.

class App < Grape::API
  mount Acme::V2
  mount Acme::V1
end

Try it on my demo project in https://github.com/dblock/grape-on-rack-v1-inside-v2.

By default we get the root of v2 and the only_in_v1 method implemented on v1.

curl https://localhost:9292/
{"version":"v2"}

curl https://localhost:9292/only_in_v1
{"only_in_v1":"true"}

With version two, we get the same thing.

curl https://localhost:9292 -H "Accept:application/vnd.acme-v2+json"
{"version":"v2"}

curl https://localhost:9292/only_in_v1 -H "Accept:application/vnd.acme-v2+json"
{"only_in_v1":"true"}

With version 1 we get the old behavior.

curl https://localhost:9292 -H "Accept:application/vnd.acme-v1+json"
{"version":"v1"}

curl https://localhost:9292/only_in_v1 -H "Accept:application/vnd.acme-v1+json"
{"only_in_v1":"true"}

The only fix needed in Grape for this was to enable multiple versions specified with version, committed in 2be499c51.

This came up on the Grape mailing list.