A complete guide for building microservices with Ruby on Rails – Part II

Assumption: You are referring to this sample API application along these series of posts.

In last post we covered introduction about Restful API’s and difference between the normal rails and API only rails application.

In this post we will see

  • What and why of API versioning?
  • Which strategy should be followed while versioning API’s?
  • Versioning of API’s using versionist gem.
  • Versioned API responses using active_model_serializers.

What and why of API versioning?

API versioning means keeping backward compatibility without breaking existing api while adding new features, to handle situations like following, in first place you should write versioned API’s.

Let’s say you have an API to sign-in users with username and password into the system and this API is being used by multiple clients like web, mobile etc.

curl -X POST \
 http://localhost:3000/users/login \
 -H 'Accept: application/vnd.mycompany.com; version=v1' \
 -H 'Content-Type: application/json' \
 -d '{"username": "pramod", "password": "passwd" }'

Now as a new requirement you want to allow users to sign-in using mobile number and otp.

What you’ll do? If you start updating exiting API it will break for existing API clients, in such scenarios its better to release new sign-in API with version v2 keeping backward compatibility and without breaking anything.

curl -X POST \
 http://localhost:3000/users/login \
 -H 'Accept: application/vnd.mycompany.com; version=v2' \
 -H 'Content-Type: application/json' \
 -d '{"number": "1234", "otp": "1234" }'

Various strategies of API versioning

There are various strategies for API versioning, here strategy meaning where to specify API version while requesting an API like in headers or in url path or as a url parameter.

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

curl -X POST  http://localhost:3000/users/login?version=v1

Path

This strategy uses a URL path prefix to request a specific version of your API.

curl -X POST  http://localhost:3000/v1/users/login

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

curl -X POST http://localhost:3000/users/login \
     -H 'Accept: application/vnd.mycompany.com; version=v2'

Which strategy you should be following?

Out of three mentioned strategies which you should be following? answer is header strategy if you ask me why? then you didn’t understood restfulness other two strategies breaks the restful principle of URL must identify/locate the resource it should specify the version of resource, headers is the place where you should specify the resource version.

Adding first API version as v1

As in previous post we have seen how to generate api-only rails application, now let’s add first API version.

Creating V1 namespace and version v1

$rails g versionist:new_api_version v1 V1 --header=name:Accept value:"application/vnd.expense-manager.com; version=1"
Key things about versionist:new_api_version command
  1. Generates V1 controller namespace for api version v1 
  2. Using header strategy with name: Accept and value:”application/vnd.expense-manager.com; version=1″
  3. Adds following route block
api_version(
  module: "V2",
  header: {
    name: "Accept",
    value: "application/vnd.expense-manager.com; version=2"
  }
) do
  #API v1 routes goes here...
end

Creating v1 users controller

#Adding app/controllers/v1/users_controller.rb
$rails g versionist:new_controller users V1

At this point our V1::UsersController will look like

#app/controllers/v1/users_controller.rb
class V1::UsersController < V1::BaseController
  def index
    render json: User.all, each_serializer: V1::UserSerializer
  end
end

If you have noticed we are using Active model serializers for versioned responses. Explaining Active model serializers will need a whole new post, I would like to leave it as an exercise. V1::UserSerializer for version v1 response is as follows

#app/serializers/v1/user_serializer.rb
class V1::UserSerializer < ActiveModel::Serializer
  attributes :id, :email

  has_many :expenses
end

With above change we will end-up having following users routes for version v1

Screen Shot 2018-08-01 at 5.07.58 PM

Adding version v2 for API

We can add version v2 for users api’s using versionist generator commands and version v2 routes block, very similar to that of version v1. If you have followed everything correctly and add V2::UsersController and V2::UserSerializer making changes that are required in v2 then your controller, erializer  and routes should look something like following.

V2::UsersController:

#app/controllers/v2/users_controller.rb
class V2::UsersController < V1::UsersController
  def index
    render json: User.all, each_serializer: V2::UserSerializer
  end
end

V2::UserSerializer: 

#app/serializers/v2/user_serializer.rb
class V2::UserSerializer < ActiveModel::Serializer
  attributes :id, :email, :total_expenses

  def total_expenses
    object.expenses.sum(:amount)
  end
end

Notice the change in response for v2.

#In config/routes.rb
api_version(
  module: "V2",
  header: {
    name: "Accept",
    value: "application/vnd.expense-manager.com; version=2"
  }
) do
  resources :users
end

api_version(
  module: "V1",
  header: {
    name: "Accept",
    value: "application/vnd.expense-manager.com; version=1"
  },
) do
  resources :users
end

Now, start rails server and let’s see things in action! You will get JSON response for v1/users and v2/users API.

curl -X GET http://localhost:3000/users \
     -H 'Accept: application/vnd.expense-manager.com; version=1'

curl -X GET http://localhost:3000/users \
     -H 'Accept: application/vnd.expense-manager.com; version=2'

Note: If you are running code from this sample API application just change following line in class V1::UsersController < V1::BaseController as we yet to see how to authenticate API’s.

skip_before_action :authenticate!, only: [:login]
to
skip_before_action :authenticate!

You might have missed!

V2::UsersController is getting inherited from V1::UsersController this is important for supporting backward compatibility, means if you have another API in V1::UsersController like /users/expenses as following

#app/controllers/v1/users_controller.rb
class V1::UsersController < V1::BaseController
  def index
    render json: User.all, each_serializer: V1::UserSerializer
  end

 def expenses
  render json: @current_user.expenses, each_serializer: V1::ExpenseSerializer
 end
end

Then both the versions of /users/expenses will work, without adding it into V2::UsersController.


curl -X GET http://localhost:3000/users/expenses \
-H 'Accept: application/vnd.expense-manager.com; version=1'

curl -X GET http://localhost:3000/users/expenses \
-H 'Accept: application/vnd.expense-manager.com; version=2'

As we have seen how to add API versions keeping backward compatibility with the recommended strategy for versioning, We can conclude the part II of this series and see you soon with the next post on API Authentication.

If you have any queries please post in comments section and keep exploring!

Advertisements

Add your thoughts...

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.