挨拶
自称、Ruby on Rails フロントエンドエンジニア🤣のnaofumiです。X @naofumiでは色々勝手なことを書いていますが、最近Qiitaも頑張り始めました。
それでは早速本題です!
ええっ!!??
37signals商品のRailsコードって見られるの?
そう!Ruby on Railsを開発した37signals社が、どのようにこのフレームワークを活用し、どのようなコードを書いているかを見ることができるのだ!
細かい話は省くが、7月3日に37signals社はWritebookというアプリを新規に公開した。自由にダウンロードでき、自分でサーバ(例えばさくらインターネットのサーバとか)を用意すれば、簡単にインストールできる。(Wordpressのような感覚)
ソースコードも入手されるので、37signals社がどのようにRuby on Railsのコードを書いているかも簡単に確認できる。実際、気になったポイントをXにポストしている人もいる。
私はまだそこまでやっていないのだが、"Service Object"っぽいのが話題になっていたので、まずはこれについて話をしたい。
"Service Object"っぽいやつ
これはMatt Swanson氏のポスト。Matt Swanson氏はBoring RailsというWebサイトと、YAGNIというポッドキャストのホストとして著名である。特に"Service Objects w/ Jason Swett"というわずか1分半のポッドキャストがあるので、英語がほとんど聞き取れないという方でも是非お勧めする😃!
Writebookのソースコードで見つけた"Service Object"っぽいやつについて、先日ポストをしていたので、これについて下記に解説したい。
なぜこのパターンを使ったのか?
このパターンを使う根拠は、私の経験上、こんなことを考えたのではないかと推測される
- 1つのコントローラアクションの中で3つのレコード(
Account
,User
,DemoContent
)を作らなければならない。Account
ActiveRecordモデルに処理を押し込んでも良いのだが、若干無理がある。それよりは専用のFirstRun
クラスを作った方が良いと判断したのだろう - この場合、
FirstRun
クラスはDDD本でEric Evansが紹介したいわゆる"Service Object"と言える
Eric Evans, "Domain Driven Design"より
なお、Eric Evansは「Service Objectの形はかくあるべき!」なんてことは言っておらず、モデルのような格好にしたりもするよって述べている。
要は、ここで使われている"Service Object"っぽいものは、Eric Evansの"Domain Driven Design"本で紹介されているもの(のうちの1つ)とほぼピッタリ一致しているのだ。
(Railsの世界では、"*Service"という動詞っぽい名前をつけて、#call
というメソッドを持たせたものが"Service Object"だという考え方もある。でもその必要は全然ないよってことをここで確認したい)
このパターンはRailsのルーティングと相性が良い
Railsはリソースベースのルーティングがデフォルトなので、リソース(モノ・名詞)をCRUDしていくように考えると、設計がすっきりする。
私はWritebookのソースを見ていないので予想だが、Writebookのコントローラとルータはおそらくこんな使い方をしていると予想される。ほとんどbin/rails g scaffold FirstRun
をやった時のような、scaffoldそのままの形に非常に近いと推測している。
上述のモデルのような格好をした"Service Object"ならば、Railsのリソースベースルーティングにスポッとハマるのだ。
# コントローラ
class FirstRunsController < ApplicationController
# GET /first_run/new
def new
end
# POST /first_run
def create
FirstRun.create!(user_params)
redirect_to ...
rescue e => ActiveRecord::RecordInvalid
render :new, status: :unprocessable_entity
end
end
# routes.rb
...
resource :first_run
...
まとめ
- 37signalsのコードを見ると、Eric Evansが紹介した、「モデルの格好をしたService object」が使われている
- 「モデルの格好をしたService object」はRailsのルーティングと相性が良い。これも無関係ではないだろう
- Eric EvansのDDD本にもあるパターンなので、37signalsとかRails固有のものではない
- ただしRailsで"Service object"と言うと
#call
を呼ぶものを連想してしまう。それもあって、「モデルの格好をしたService object」は"PORO" (Plain Old Ruby Object: ただのRubyオブジェクト)と呼ぶことが多い(上述のJason Swettなどもそうしているという認識) - ちゃんとしたモデリング上の理由があって、こういうPOROが使われている。"Service Object"を使う理由として、「コントローラが太ってしまうから」なんて言う人がよくいるが、そんな安っぽい理由で導入しているパターンではない
そしてRailsでよく使われている、#call
を呼ぶ形のService Objectについては
- Service Objectは
#call
で呼ぶ形である必要はないし、名前を動詞的にする必要もない。やっても良いがこれと言ったメリットはない -
#call
で呼ぶ形を使っても良いのだが、Railsのリソースベースのルートと相性がよくないので、私としてはあまりお勧めしない - また私見だが、機能追加していくと、最初はPOROで作ったモデルの重要さが増えていって、最後には普通のActiveRecordモデルになることはよくある。
#call
を呼ぶ形よりはPOROの方の拡張性が高いと思う