Enterprise Integration Zone is brought to you in partnership with:

Daniel Doubrovkine (aka dB.) is one of the tallest engineers at Art.sy. He founded and exited a successful Swiss start-up in the 90s, worked for Microsoft Corp. in Redmond, specializing in security and authentication, dabbled in large scale social networking and ran a big team that developed an expensive Enterprise product in NYC. After turning open-source cheerleader a few years ago in the worlds of C++, Java and .NET, he converted himself to Ruby and has been slowly unlearning everything he learned in the last 15 years of software practice. Daniel has posted 46 posts at DZone. You can read more from them at their website. View Full User Profile

Evolving APIs using Grape API Versioning

08.05.2013
| 2031 views |
  • submit to reddit

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 next release of Grape (likely 0.6.0) you will 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 and allow v2 to cascade the request to the next Rack middleware by adding cascade: true to its version declaration. Rack::Cascade works by passing on any requests until a middleware replies with something else than a 404.

new Rack::Cascade([ Acme::V2, Acme::V1 ])
version 'v2', using: :header, vendor: 'acme', format: :json, cascade: true


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 ping method implemented on v1.

    curl http://localhost:9292/
    {"version":"v2"}
     
    curl http://localhost:9292/ping
    {"ping":"pong"}
With version two, we get the same thing.
    curl http://localhost:9292 -H "Accept:application/vnd.acme-v2+json"
    {"version":"v2"}
     
    curl http://localhost:9292/ping -H "Accept:application/vnd.acme-v2+json"
    {"ping":"pong"}
With version 1 we get the old behavior.
    curl http://localhost:9292 -H "Accept:application/vnd.acme-v1+json"
    {"version":"v1"}
     
    curl http://localhost:9292/ping -H "Accept:application/vnd.acme-v1+json"
    {"ping":"pong"}
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.

Published at DZone with permission of its author, Daniel Doubrovkine. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)