RailsのControllerにApplicationService相当のロジックを書くのはありなしや?

誤解を産んでいそうだったので追記します。ここでいうApplicationServiceというのは、いわゆるレイヤードアーキテクチャのアプリケーション層のApplicationServiceレイヤの話です。別の言葉だと、「Usecase層」とか言う言葉で呼ばれたりするアレのことです。追記おしまい。

これについてです。

結論から先にいうと、ぼくは「正しい」と思っています(ただ、自分ではあまりやらず、ApplicationServiceに切り出しちゃいますが、これは好みの問題だと思っています)。

なぜ正しいと思うのか。それを考える際に、まずは「一般的に、なぜControllerとApplicationServiceを分けるべきなのか」について考えてみたいと思います。ControllerとApplicationServiceを分けるのは、PDSの文脈において、ControllerがPresentation(ユーザーとのインタラクションに関わる部分)のレイヤーに属すものであり、ApplicationServiceはDomainに属すものである、という理解がまずあるからだと思います。

では、なぜPDSが重要なのでしょうか。原典を引きましょう

  • プレゼンテーションロジックとドメインロジックが分かれていると、理解しやすい
  • 同じ基本プログラムを、重複コードなしに、複数のプレゼンテーションに対応させることができる
  • ユーザーインターフェイスはテストがしにくいため、それを分離することにより、テスト可能なロジック部分に集中できる
  • スクリプト用のAPIやサービスとして外部化するためのAPIを楽に追加できる(選択可能なプレゼンテーション部分で見かける)
  • プレゼンテーション部分のコードは、ドメイン部分のコードと違ったスキルと知識が必要

Martin Fowler's Bliki (ja)より

ぼくのことばで言い換えると、

  • プレゼンテーションはテストしにくいから、ドメインに分けておくとテストがしやすくなる
  • 責務が明確に分離されてわかりやすくなる
  • 他のプレゼンテーションレイヤーに差し替えが可能(Railsの例で言えば、ApplicationServiceに分離しておけば、たとえばTaskからも呼べる)

の三点が、PresentationとDomainを分離する際の嬉しいポイントです。ここで、RailsにおいてControllerにApplicationServiceを書いたときにどれだけこの問題が顕在化するかについて考えてみます。

まずは「プレゼンテーションはテストしにくいから、ドメインに分けておくとテストがしやすくなる」についてです。これ実はRails使ってるとあんま問題じゃなくて、というのもRailsってControllerのテストめっちゃふつうにしやすいじゃないですか。だからこれはあんまり問題にならないと思っています。

続いて、「責務が明確に分離されてわかりやすくなる」です。これについてはちょっと丁寧に見ていきましょう。おそらく、RailsでControllerとApplicationServiceを分離すると、Controllerの各メソッドは「1,2行書いておしまい」というメソッドになるでしょう(そうじゃなかったらちゃんと分離できてないと思ったほうがよさそう)。こうなると、「Controllerいる?」って感じになってきますよね。これが静的型付け言語の場合で、しかもVannila DIだったりCakePatternしてる場合なんかは、Controller部分でDIしてたりするので、Controllerに責務が残りますよね。あと、MVVMで作ってるGUIアプリケーションの場合、ローカルステートの管理などがVMの責務として残ります。けど、Railsの場合、ApplicationServiceをControllerから切り出すとそこに責務ってほとんど残んないんですよね……。あるとしたら認証とかかな。けどこれもControllerのテスト対象メソッドでやるんじゃなくて普通はレイヤスーパータイプとかでやってますよね。というわけで、なんか特殊なHTTPの言葉の部分触ってるとかそういうのがないのであれば(あーセッション周りとかはなにかしらあるかもですね)(しかしセッションって本来Infrastructureだよな〜)、「RailsにおいてはApplicationService相当のロジックをControllerに書くとする!」ってするのは十分にメイクがセンスするし、パルスのファルシのルシがパージでコクーンなのではないかとぼくは思います。

最後に、「他のプレゼンテーションレイヤーに差し替えが可能(Railsの例で言えば、ApplicationServiceに分離しておけば、たとえばTaskからも呼べる)」についてです。ここについては実はControllerにApplicationService相当のコードを書いてしまうと問題が顕在化します。TaskからApplicationServiceを呼び出すのはとても簡単ですが、TaskからControllerを呼び出すのは簡単ではありません。しかし、ちょっと立ち止まって考えてみると、ほんとうにそんな柔軟さはRailsアプリに必要なのでしょうか???今まで、「あー、TaskからController呼び出せたら最高なのにな〜」って思ったこと、あります? ぼくはほとんどないです。そのために新たな層を導入して複雑さを引き受けるの、コストがペイするのでしょうか?パルスのファルシのルシがパージでコクーンなのでしょうか?

しかし、じつは、ここがぼくの「趣味」の部分で、ぼくはこれのためにApplicationServiceを分離してんですね。なぜか。そうすることで、たとえばテストの、しかも「前提データ」を用意するときに嬉しいことが起こります。Rails文化ではなぜか(なぜかと言ってしまおう)FactoryBotが人気で、FactoryBotを利用して前提データ(たとえば、ログイン可能なユーザーアカウントとかね)を作るプラクティスが横行していますが、ぼくは以前から、なぜここでみんな(たとえば)「UserSignupApplicationService」を作ってそれを呼び出すだけにしないんだろう、と不思議に思っています。適切にApplicationServiceが書かれていれば、UserSignupApplicationServiceを呼び出すだけで前提データは作れるはずです。FactoryBotを利用して前提データを作る場合、「サインアップ」の仕様が変わると「UserSignupApplicationService」と、「サインアップ済みユーザー」を作り出すFactoryBotを使った部分両方のメンテが必要になってきます。一方、FactoryBotではなくてApplicationServiceを利用すれば、ApplicationServiceのメンテのみで済みます。なぜ「テスト済みの、前提データを作れる部品」があるのに、FactoryBotを使う必要があるのでしょうか?

という観点に立つと、意外とApplicationServiceを分離することには価値がありそうですが、世の中的にはFacyoryBotが主流だし、「Railsにおいて」という前提に乗っかるなら、これはかなり「異端児の意見」であろうな、というのもわかるので、アンケートの答えとしては「正しい」を選ぶ、けど自分はあまりやらない。という感じになるわけでした!

以上、ぼくの意見です。みなさんの意見もぜひ聞かせてください。