3
0

37signalsが書くコードの"Service Object"を見てみる

Posted at

挨拶

自称、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. 1つのコントローラアクションの中で3つのレコード(Account, User, DemoContent)を作らなければならない。Account ActiveRecordモデルに処理を押し込んでも良いのだが、若干無理がある。それよりは専用のFirstRunクラスを作った方が良いと判断したのだろう
  2. この場合、FirstRunクラスはDDD本でEric Evansが紹介したいわゆる"Service Object"と言える

Eric Evans, "Domain Driven Design"より
image.png

なお、Eric Evansは「Service Objectの形はかくあるべき!」なんてことは言っておらず、モデルのような格好にしたりもするよって述べている。

image.png

要は、ここで使われている"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
...

まとめ

  1. 37signalsのコードを見ると、Eric Evansが紹介した、「モデルの格好をしたService object」が使われている
  2. 「モデルの格好をしたService object」はRailsのルーティングと相性が良い。これも無関係ではないだろう
  3. Eric EvansのDDD本にもあるパターンなので、37signalsとかRails固有のものではない
  4. ただしRailsで"Service object"と言うと#callを呼ぶものを連想してしまう。それもあって、「モデルの格好をしたService object」は"PORO" (Plain Old Ruby Object: ただのRubyオブジェクト)と呼ぶことが多い(上述のJason Swettなどもそうしているという認識)
  5. ちゃんとしたモデリング上の理由があって、こういうPOROが使われている。"Service Object"を使う理由として、「コントローラが太ってしまうから」なんて言う人がよくいるが、そんな安っぽい理由で導入しているパターンではない

そしてRailsでよく使われている、#callを呼ぶ形のService Objectについては

  1. Service Objectは#callで呼ぶ形である必要はないし、名前を動詞的にする必要もない。やっても良いがこれと言ったメリットはない
  2. #callで呼ぶ形を使っても良いのだが、Railsのリソースベースのルートと相性がよくないので、私としてはあまりお勧めしない
  3. また私見だが、機能追加していくと、最初はPOROで作ったモデルの重要さが増えていって、最後には普通のActiveRecordモデルになることはよくある。#callを呼ぶ形よりはPOROの方の拡張性が高いと思う
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0