Rails API 使ってみた

昨日 [Ginza.rb 第23回 APIのみのRailsアプリ「Rails API」を読もう！](https://ginzarb.doorkeeper.jp/events/24866) で Ginza.rb に初参加してきたので復習メモ。

ちなみに Ginza.rb は二周年だそうですよ。おめでとうございます！

Ginza.rb の内容は Rails5 でとりこまれることが決まって目下議論中らしい Rails API についてのコードリーディングでした。

- [pull request](https://github.com/rails/rails/pull/19832)
- [RAILS API TO BE PART OF RAILS 5](http://wyeworks.com/blog/2015/4/20/rails-api-is-going-to-be-included-in-rails-5/)
- [Rails API](https://github.com/rails-api/rails-api)
- [ginza.rb 23th Rails API](http://qiita.com/ken1flan/items/84c328fec78ae578b953)

まだ Rails 本体にマージされていないので、普通の rails と rails-api で比較しながら使ってみました。

### アプリ作成

```console
$ gem install rails-api
$ rails new blog_app # 普通の rails
$ rails-api new blog_api # API な rails
```

### middleware 比較

Rails API は使用している middleware を単に減らしているだけぽかった。比較してみる。

```
blog_app$ ./bin/rake middleware > app.middleware
blog_api$ ./bin/rake middleware > api.middleware
```
```diff:app_api.diff
1d0
< use Rack::Sendfile
4c3
< use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fa99ccebcb8>
---
> use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007f5618cc2200>
6d4
< use Rack::MethodOverride
10d7
< use WebConsole::Middleware
18,20d14
< use ActionDispatch::Cookies
< use ActionDispatch::Session::CookieStore
< use ActionDispatch::Flash
25c19
< run BlogApp::Application.routes
---
> run BlogApi::Application.routes
```

Rails API 側で追加の middleware はなさそう。

### Scaffold 追加

Entry という title, body を持つモデルを追加してみる。

```
blog_app$ ./bin/rails g scaffold entry title:string body:text
- snip 5 lines -
      create      test/fixtures/entries.yml
      invoke  resource_route
       route    resources :entries
      invoke  scaffold_controller
- snip 23 lines -

blog_api$ ./bin/rails g scaffold entry title:string body:text
- snip 5 lines -
      create      test/fixtures/entries.yml
      invoke  api_resource_route
       route    resources :entries, except: [:new, :edit]
      invoke  scaffold_controller
- snip 3 lines -
```

コードリーディング通り view(erb, jbuilder), helper, assets なんかが生成されず `api_resource_route` が invoke されて `:new`, `:edit` が生成されていないことが確認できた。

## GET

Entry を一個追加して GET してみる。

```
blog_app$ ./bin/rails s -p 3000
$ curl http://localhost:3000/entries.json
[{"id":1,"title":"title","body":"body","url":"http://localhost:3000/entries/1.json"}]

blog_api$ ./bin/rails s -p 3001
$ curl http://localhost:3001/entries
[{"id":1,"title":"title","body":"body","created_at":"2015-05-XXTXX:XX:XX.XXXZ","updated_at":"2015-05-XXTXX:XX:XX.XXZ"}]
```

Rails APP はタイムスタンプがなく url というのが追加されてた。

ちなみにそれぞれのログが

```
# blog_app
Started GET "/entries.json" for 127.0.0.1 at 2015-05-XX XX:XX:XX +0900
Processing by EntriesController#index as JSON
  Entry Load (0.2ms)  SELECT "entries".* FROM "entries"
  Rendered entries/index.json.jbuilder (1.3ms)
Completed 200 OK in 3ms (Views: 2.7ms | ActiveRecord: 0.2ms)

# blog_api
Started GET "/entries" for 127.0.0.1 at 2015-05-XX XX:XX:XX +0900
Processing by EntriesController#index as */*
  Entry Load (0.2ms)  SELECT "entries".* FROM "entries"
Completed 200 OK in 1ms (Views: 1.0ms | ActiveRecord: 0.2ms)
```

となっていて Rails API は jbuilder で render してないことが確認できた。その分速い。

## ベンチマーク

```
$ ab -n 1000 -c 10 http://localhost:3000/entries.json
$ ab -n 1000 -c 10 http://localhost:3001/entries
```

||Rails APP|Rails API|
|:-----------|------------:|------------:|
|Server Software|WEBrick/1.3.1|WEBrick/1.3.1|
|Server Hostname|localhost|localhost|
|Server Port|3000|3001|
|||
|Document Path|/entries.json|/entries|
|Document Length(bytes)|85|120|
|||
|Concurrency Level|10|10|
|Time taken for tests(seconds)|10.160|7.085|
|Complete requests|1000|1000|
|Failed requests|0|0|
|Total transferred(bytes)|539000|575000|
|HTML transferred(bytes)|85000|120000|
|Requests per second(\[#/sec\] (mean))|98.42|141.14|
|Time per request(\[ms\] (mean))|101.604|70.851|
|Time per request(\[ms\] (mean, across all concurrent requests))|10.160|7.085|
|Transfer rate(\[Kbytes/sec\] received)|51.81|79.25|

Requests per second で比較すると Rails API の方が 1.43 倍くらい速いことが確認できた。

## Rails APP で jbuilder 使わない

APP 側で jbuilder での render を止めてみて再度ベンチマークとってみる。

```ruby
  def index
    @entries = Entry.all

    respond_to do |format|
      format.html
      format.json { render json: @entries.to_json, status: 200 and return }
    end
  end
```

curl してみる。

```
$ curl http://localhost:3000/entries.json
[{"id":1,"title":"title","body":"body","created_at":"2015-05-XXTXX:XX:XX.XXZ","updated_at":"2015-05-XXTXX:XX:XX.XXXZ"}]
```

url がなくなって Rails API の結果と要素が同じになった。
ログを確認してみる。

```
Started GET "/entries.json" for 127.0.0.1 at 2015-05-XX XX:XX:XX +0900
Processing by EntriesController#index as JSON
  Entry Load (0.2ms)  SELECT "entries".* FROM "entries"
Completed 200 OK in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms)
```

jbuilder の render がなくなったのが確認できたので、再度ベンチマークとってみた。

||Rails APP w/o jbuilder|Rails API|
|:-----------|------------:|------------:|
|Server Software|WEBrick/1.3.1|WEBrick/1.3.1|
|Server Hostname|localhost|localhost|
|Server Port|3000|3001|
|||
|Document Path|/entries.json|/entries|
|Document Length(bytes)|120|120|
|||
|Concurrency Level|10|10|
|Time taken for tests(seconds)|8.389|7.085|
|Complete requests|1000|1000|
|Failed requests|0|0|
|Total transferred(bytes)|575000|575000|
|HTML transferred(bytes)|120000|120000|
|Requests per second(\[#/sec\] (mean))|119.20|141.14|
|Time per request(\[ms\] (mean))|83.894|70.851|
|Time per request(\[ms\] (mean, across all concurrent requests))|8.389|7.085|
|Transfer rate(\[Kbytes/sec\] received)|66.93|79.25|

ちょっと速くなった。

## 結論

「初参加の方から一言」通り、やっぱり

__RailsAPI使わないかもしれない…__

というよりは Rails APP w/o jbuilder で十分な気がしなくもない。

pull request の中でシリアライズに [active_model_serializers](https://github.com/rails-api/active_model_serializers) をデフォルトで使用するみたいなことが議論されている？ので本体に正式にマージされたら、あらためてベンチマークをとってみたいと思います。

適当にでっちあげた投稿(次回の Ginza.rb のネタ？)なので、間違いとかあれば指摘していただけると助かります。