こんにちは。メドピアエンジニアの保立です。
メドピアでは、ドクター向けに運営している「MedPeer」のiOSアプリを3月15日にリリース致しました!!!
今回は、iOSアプリ開発の過程で、APIの実装にActiveModelSerializersを使ったので、そこで得た知見を書きます。
ActiveModelSerializers を使った理由
json形式のレスポンスを返却する場合、jbuilderを使うケースも多いのではないでしょうか。
メドピアでも、今まで外部へのAPIにはjbuilderを使用していました。
しかし、iOSアプリ用のAPIでは、以下のメリットを考慮して、ActiveModelSerializersを使うことにしました。
メリット① 複雑なjsonを返す際に、ActiveModelSerializersの方が、レスポンスが早い
スマートフォン用のAPIでは、なるべく少ないリクエストで画面の描画に必要なすべての情報を返却したいため、何重にも入れ子になるjsonを返却することが多いと思います。そのため、viewをレンダリングするjbuilderでは汎用的なオブジェクトはpartial化することが求められますが、partialを呼び出すのに時間がかかってしまいます。
一方、ActiveModelSerializersでは、レスポンスの内容を定義するserializerにhas_many
やhas_one
を使って、関連するオブジェクトを指定することができます。これにより、jbuilderよりも素早くレスポンスを返すことができます。
メリット② DSLな書き方をしなくてよい
jbuilderの書き方は、直感的に分かりづらい記法になるケースがあります。
json.title @post.title json.body @post.body # {"title": "タイトル", "body": "本文"}
json.post @post, :title, :body # {"post": {"title": "タイトル", "body": "本文"}}} # これも同じ内容を返す(こっちの方が少し分かりやすい) json.post do json.title @article.title json.body @article.body end
さらに、partialを読んだり、if文などの分岐が入ると、より分かりづらくなります。
ActiveModelSerializersでは、Rubyの記法に則って書けるため、分かりやすいコードになります。
以上の点から、メドピアのiOSアプリ用APIとして、ActiveModelSerializersを採用しました。
使用例
ActiveModelSerializerは以下のように使用します。
class PostsController < ApplicationController def show @post = Post.find(params[:id]) render json: @post, serializer: PostSerializer end end
class PostSerializer < ActiveModel::Serializer attribute :title # 別名をつけたい時はkeyを使用する attribute :created_at, key: :timestamp # has_manyやhas_oneで関連するオブジェクトを指定する has_many :comments, serializer: CommentSerializer # メソッドを呼び出すことも可能 attribute :published def published object.published_at.present? end end class CommentSerializer < ActiveModel::Serializer attribute :body end # レスポンス { title: "タイトル", timestamp: "2018-01-01T00:00:00+09:00", published: true, comments: [ { body: "コメント" } ] }
並列関係の複数Modelに紐づくAPIを返却したい場合もあります。
以下の例は、アプリ起動時など、Masterデータを取得する時のコードです。
class MasterDataController render json: MasterData.new, serializer: MasterDataSerializer end
class MasterData def first_master_data @first_master_data ||= Master::FirstMasterData.all end def second_master_data @second_master_data ||= Master::SecondMasterData.all end end
class MasterDataSerializer has_many :first_master_data has_many :second_master_data class FirstMasterDataSerializer attributes :id, :name end class SecondMasterDataSerializer attributes :id, :name end end
使ってみた感想
書いていて、ものすごくつまずくということがなかったです。
ActiveRecordの延長のような作りなので、つまずくことなく書くことができました。
またGitHubのドキュメントが豊富なので、基本的にわからないことがあってもドキュメントをみれば解決しました。
コード量はjbuilderより多くなるので、「すぐに実装したい」「複雑なjsonを返す必要がない」場合はjbuilderでもいいと思います。
逆に、長い間運用したり、拡張する可能性があるAPIを用意する場合は、ActiveModelSerializerおすすめです^^
おまけ(Active Model Serializer と jbuilder のパフォーマンス比較)
posts#showアクションにて、postsを1件とhas_many関係のcommentsを20件をレスポンスに設定するケースで比較します。
jbuilder
# app/controllers/posts_controller.rb class PostsController < ApplicationController def show @post = Post.find(params[:id]).preload(:comments) end end # app/views/posts/show.json.jbuilder json.title @post.title json.page_views do json.partial! partial: 'comment', collection: @post.comments, as: :comment end # app/views/posts/_comments.json.jbuilder json.body comments.body
実行結果は以下のようになります。
Processing by PostsController#show as JSON Posts Load (1.0ms) SELECT `posts`.* FROM `posts` LEFT JOIN `comments` ON `posts`.`id` = `comments`.`post_id` Rendered posts/_comments.jbuilder (0.3ms) Rendered posts/_comments.jbuilder (0.3ms) Rendered posts/_comments.jbuilder (0.3ms) (commentsの数だけ続く) Rendered posts/_comments.jbuilder (0.3ms) Rendered posts/show.json.jbuilder (47.2ms) Completed 200 OK in 87ms (Views: 49.6ms | ActiveRecord: 7.5ms)
Active Model Serializers
# app/controllers/posts_controller.rb class PostsController < ApplicationController def show @post = Post.find(params[:id]).preload(:comments) render json: @post, serializer: PostSerializer end end # app/serializers/posts_serializer.rb class PostSerializer < ActiveModel::Serializer attribute :title # has_manyやhas_oneで関連するオブジェクトを指定する has_many :comments, serializer: CommentSerializer end class CommentSerializer < ActiveModel::Serializer attribute :body end
実行結果は以下のようになります。
Processing by PostsController#show as HTML Posts Load (1.0ms) SELECT `posts`.* FROM `posts` LEFT JOIN `comments` ON `posts`.`id` = `comments`.`post_id` [active_model_serializers] Rendered PostSerializer with ActiveModelSerializers::Adapter::Attributes (11.23ms) Completed 200 OK in 41ms (Views: 9.7ms | ActiveRecord: 7.9ms)
と、いうことで2倍以上の速度でレスポンスを返すことができました。
実際のMedPeerアプリでは、3重4重に入れ子にしたjsonを返却することがあるため、Active Model Serializers の恩恵は計り知れないものがあります。
(☝︎ ՞ਊ ՞)☝︎是非読者になってください
メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら
https://medpeer.co.jp/recruit/entry/
■開発環境はこちら