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

## 挨拶

自称、Ruby on Rails フロントエンドエンジニア🤣のnaofumiです。X [@naofumi](https://x.com/naofumi)では色々勝手なことを書いていますが、最近Qiitaも頑張り始めました。

それでは早速本題です！

## ええっ！！？？ <br>37signals商品のRailsコードって見られるの？

そう！Ruby on Railsを開発した37signals社が、どのようにこのフレームワークを活用し、どのようなコードを書いているかを見ることができるのだ！

細かい話は省くが、7月3日に37signals社は[Writebookというアプリを新規に公開した](https://world.hey.com/jason/introducing-writebook-e217cae3)。自由にダウンロードでき、自分でサーバ（例えばさくらインターネットのサーバとか）を用意すれば、[簡単にインストール](https://books.37signals.com/2/the-writebook-manual/27/installation)できる。（Wordpressのような感覚）

ソースコードも入手されるので、37signals社がどのようにRuby on Railsのコードを書いているかも簡単に確認できる。実際、気になったポイントをXにポストしている人もいる。

私はまだそこまでやっていないのだが、"Service Object"っぽいのが話題になっていたので、まずはこれについて話をしたい。

## "Service Object"っぽいやつ

これはMatt Swanson氏のポスト。Matt Swanson氏は[Boring Rails](https://t.co/67BOoEsgTv)というWebサイトと、[YAGNI](https://t.co/MMIvlQpzwD)というポッドキャストのホストとして著名である。特に["Service Objects w/ Jason Swett"](https://yagni.fm/episodes/service-objects-w-jason-swett)というわずか1分半のポッドキャストがあるので、英語がほとんど聞き取れないという方でも是非お勧めする😃！

Writebookのソースコードで見つけた"Service Object"っぽいやつについて、先日ポストをしていたので、これについて下記に解説したい。

https://x.com/_swanson/status/1809357797966115299

## なぜこのパターンを使ったのか？

このパターンを使う根拠は、私の経験上、こんなことを考えたのではないかと推測される

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

**Eric Evans, "Domain Driven Design"より**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3507700/b2d692ef-ab7c-6433-1b33-23f4d7e54a42.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3507700/e97d14f3-e1b2-196c-b67b-ea005c635e14.png)

**要は、ここで使われている"Service Object"っぽいものは、Eric Evansの"Domain Driven Design"本で紹介されているもの（のうちの１つ）とほぼピッタリ一致しているのだ。**

（Railsの世界では、"*Service"という動詞っぽい名前をつけて、`#call`というメソッドを持たせたものが"Service Object"だという考え方もある。でもその必要は全然ないよってことをここで確認したい）

## このパターンはRailsのルーティングと相性が良い

[Railsはリソースベースのルーティングがデフォルト](https://railsguides.jp/routing.html#リソースベースのルーティング-railsのデフォルト)なので、リソース（モノ・名詞）をCRUDしていくように考えると、設計がすっきりする。

私はWritebookのソースを見ていないので予想だが、Writebookのコントローラとルータはおそらくこんな使い方をしていると予想される。ほとんど`bin/rails g scaffold FirstRun`をやった時のような、scaffoldそのままの形に非常に近いと推測している。

上述のモデルのような格好をした"Service Object"ならば、Railsのリソースベースルーティングにスポッとハマるのだ。

```ruby
# コントローラ
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
```

```ruby
# routes.rb
...
resource :first_run
...
```

## まとめ

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

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

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