We have a website that is built by Rails. We also serve a bunch of APIs for customers. The api server is built on Grape, an amazing REST-like API micro-framework for Ruby. Recently I uses my spare time to learn Golang, a pretty new but fast growing language, and is extremely impressed by it’s simplicity and efficiency. I prefer learn by doing, so I start this experiment to try to re-write our api using Go, I want to see how hard it is to write code in Go comparing to Ruby.

I googled around to try to find a web framework in Golang, that is suitable for building an api service, and also easy to start with. And I finally found Martini. I really like it’s simple design of routing, flexible middleware handlers, and smart Injector. In the following paragraph, I’m going to compare Grape to Martini on how to code a basic version of our api server. The complete source code can be found on github: https://github.com/steventen/grape-vs-martini.

Example Requirements

The api server just uses a key param in the query string for authentication. And it responses with a customized json format, like this

{"status": "Success", "data": [...]} # if success
{"status": "Fail", "error_message": "Bad api key"} # if failed

To simplify this experiment, we only define two models: Company and Project. A Company has many projects, and each project belongs to a company. Every company includes a unique api key for authentication. This example only implements two API endpoints:

GET /projects(.json)
GET /project/:id(.json)

Let’s see how we can implement this example on each web framework.

Implementation On Grape

Models

Our grape server is mounted on Rails. In Rails’s ActiveRecord, those two models can be represented as

class Project < ActiveRecord::Base
  belongs_to :company
end

class Company < ActiveRecord::Base
  has_many :projects
end

We also use grape-entity gem to manage model’s fields to be exposed, and alias names to be shown in json response.

module MySite
  module APIEntities
    class Project < Grape::Entity
      expose :id
      expose :name
    end
  end
end

Authentication

Authentication by api key needs to be done before every request, in Grape, you just need to put authentication method into before block, you can always define Helpers to make your code clean:

class API < Grape::API
  # ... ...

  helpers do
    def current_company
      key = params[:key]
      @current_company ||= Company.where(:api => key).first
    end

    def authenticate!
      error!({ "status" => "Fail", "error_message" => "Bad Key" }, 401) unless current_company
    end
  end

  before do
    authenticate!
  end

  # ... ...
end

Routes

Grape has very convenient get, post methods, our api routes can be coded like:

class API < Grape::API
  # ... ...

  get "projects" do
    projects = current_company.projects
    present :data, projects, :with => APIEntities::Project
    present :status, "Success"
  end

  get "projects/:id" do
    project = current_company.projects.where(id: params[:id]).first
    if project
      {"data" => {"id" => project.id, "name" => project.name}, "status" => "Success"}
    else
      # error!({ "status" => "Fail", "error_message" => "Failed to save project" }, 404)
      { "status" => "Fail", "error_message" => "Failed to save project" }
    end
  end
end

Note that Grape also provides some great helper methods like namespace, route_param for you to easily create complex routes, and methods like params to do parameter validations. Those features are not shown in this example.

With Rails’ Active Record query interface, this code is fairly simple. In github repo, there are two versions, one is built directly on pure Rack, you can view the source code here. Another version is on Rails, source code is here.

Implementation On Martini

Models

Models can be represented by struct in golang:

type Project struct {
  Id   int    `json:"id"`
  Name string `json:"name"`
}

type Company struct {
  Id  int    `json:"id"`
  Api string `json:"api_key"`
}

One nice thing is the tag syntax behind field declaration. Those string tags control how fields are interpreted during json encoding, it equals to the use of Grape Entity’s name alias.

Now we don’t have ActiveRecord, we can simply use go’s sql package to do query. sql package provides a generic interface around SQL databases, similar like Java’s JDBC. For different database, you can add associated database driver without changing your code. There is a great tutorial on how to use sql package. Since we use MySQL, we can import related package as:

import (
  "database/sql"
  _ "github.com/go-sql-driver/mysql"
)

We need these following methods:

  1. get company by api key
  2. get list of projects under certain company
  3. get project base on project id:
func GetCompany(db *sql.DB, key string) (Company, int) {
  var company_id int
  var api_key string
  err := db.QueryRow("select id, api from companies where api = ? limit 1", key).Scan(&company_id, &api_key)
  switch {
    case err == sql.ErrNoRows:
      return Company{}, 0
    case err != nil:
      fmt.Println(err)
      return Company{}, -1
    default:
      return Company{company_id, api_key}, company_id
  }
}

func GetProject(db *sql.DB, company_id int, project_id int) (Project, int) {
  var (
    id   int
    name string
  )
  err := db.QueryRow("select id, name from projects where id = ? and company_id = ? limit 1", project_id, company_id).Scan(&id, &name)
  switch {
    case err == sql.ErrNoRows:
      return Project{}, 0
    case err != nil:
      fmt.Println(err)
      return Project{}, -1
    default:
      return Project{id, name}, id
  }
}

func GetProjects(db *sql.DB, companyId int) []Project {
  projects, err := db.Query("select id, name from projects where company_id = ?", companyId)
  if err != nil {
    fmt.Println(err)
  }
  var (
    id   int
    name string
  )
  p := make([]Project, 0)
  defer projects.Close()
  for projects.Next() {
    err := projects.Scan(&id, &name)
    if err != nil {
      fmt.Println(err)
    } else {
      p = append(p, Project{id, name})
    }
  }
  return p
}

Authentication

The server code sits inside main function. We first create a martini instance with default settings:

func main() {
  ... ...
  m := martini.Classic()
  ... ...

In order to do our api key based authentication, we can use Martini’s middleware handler. The middleware handler sit between the incoming http request and the router. It can be used to do the same job as the before method in our Grape code. The code looks like this:

m.Use(render.Renderer())

m.Use(func(res http.ResponseWriter, req *http.Request, r render.Render) {
  api_key := ""
  api_key = req.URL.Query().Get("key")
  if api_key == "" {
    r.JSON(404, map[string]interface{}{"status": "Fail", "error_message": "Need api key"})
  } else {
    current_company, company_id := GetCompany(db, api_key)
    if company_id < 0 {
      r.JSON(404, map[string]interface{}{"status": "Fail", "error_message": "Bad api key"})
    } else {
      m.Map(current_company)
    }
  }
})

In the above code, we use golang’s URL.Query().Get() function for http.Request to retrieve the api key from query string. This function is very useful. There is also a ParseQuery() function, which returns a map listing the values specified for each key. Those functions can be very helpful when you want to work with POST request. For more information, please refer net/url package.

Also in the code above, we include Martini’s render middleware, which helps us rendering serialized JSON responses:

m.Use(render.Renderer())

By using Martini’s inject feature, you can pass render.Render object anywhere you want. And can simply use code like below to generate json response:

r.JSON(404, map[string]interface{}{"status": "Fail", "error_message": "Bad api key"})

One more thing to know is we also used Martini’s so-called Global Mapping:

m.Map(current_company)

In this way, current_company object can be seen globally, and are available to be injected into any Handler’s argument list, you will see the use shortly.

Routes

Routing is not hard. The code is shown as below, very intuitive:

m.Get("/projects", func(current_company Company, r render.Render) {
  projects := GetProjects(db, current_company.Id)
  r.JSON(200, map[string]interface{}{"status": "Success", "data": projects})
})

m.Get("/projects/:id", func(current_company Company, params martini.Params, r render.Render) {
  paramId, err := strconv.Atoi(params["id"])
  if err != nil {
    r.JSON(404, map[string]interface{}{"status": "Fail", "error_message": err.Error()})
    return
   }
  project, id := GetProject(db, current_company.Id, paramId)
  if id > 0 {
    r.JSON(200, map[string]interface{}{"status": "Success", "data": project})
  } else {
    r.JSON(404, map[string]interface{}{"status": "Fail", "error_message": "Project not found"})
  }
})

Note that current_company object is injected into argument list of handler functions. Besides, we have martini.Params that can be used to get params found in route. Here we use it to find project id, and use strconv package to convert it to integer.

To run your server, just use:

m.Run()

You can always use go’s original http.ListenAndServe from net/http package:

http.ListenAndServe(":8080", m)

Conclusion and Benchmark

In this post, we focus on three major parts of writing an api server: Auth, Model and Route, and then compare implementations between Ruby’s Grape and Go’s Martini. I’m still a beginner of Go, and I just learned it in less than a week, but really feel it’s fun and ease to use. As it is shown, Martini provides most of what we need to write a simple api server.

Benchmark is not the important part of this post, but I still did ab test with -c 10 -n 1000 on local machine. MySQL is used, sample data contains 10 companies, each with 50 projects, so there are 500 projects in total.

Test environment: Macbook Air CPU 1.7GHz Core i5, 8GB DDR3, OSX 10.9.1

ruby -v 2.0.0p247, go version go1.2 darwin/amd64, rails 3.2.16

Grape On Rack

Concurrency Level: 10
Time taken for tests: 16.277 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 2303000 bytes
HTML transferred: 2211000 bytes
Requests per second: 61.44 [#/sec] (mean)
Time per request: 162.769 [ms] (mean)
Time per request: 16.277 [ms] (mean, across all concurrent requests)
Transfer rate: 138.17 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 23 162 33.9 172 289
Waiting: 17 156 33.2 169 278
Total: 23 162 33.9 173 290

Grape On Rails

Concurrency Level: 10
Time taken for tests: 15.902 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 2492000 bytes
HTML transferred: 2211000 bytes
Requests per second: 62.88 [#/sec] (mean)
Time per request: 159.024 [ms] (mean)
Time per request: 15.902 [ms] (mean, across all concurrent requests)
Transfer rate: 153.03 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 15 158 23.5 170 186
Waiting: 15 158 23.5 170 185
Total: 16 158 23.5 171 186

Go Martini

Concurrency Level: 10
Time taken for tests: 0.900 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 2314000 bytes
HTML transferred: 2211000 bytes
Requests per second: 1110.80 [#/sec] (mean)
Time per request: 9.003 [ms] (mean)
Time per request: 0.900 [ms] (mean, across all concurrent requests)
Transfer rate: 2510.14 [Kbytes/sec] received

Grape on rails uses Puma server, it can get around 63 request/s, where Go server can get 1110 request/s. Go server is faster with no doubt.

Full source code with sample data can be found at github: https://github.com/steventen/grape-vs-martini