Open34

Railsでビジネスロジックをどこに置くのか問題

kappazkappaz

発端は
Sustainable Web Development with Ruby on Railsの以下の文。(DeepLによる翻訳後)

しかし、すべてのアプリの中で、Railsが明確な答え
を持っていない部分が1つあります。それは、ビジネスロジックです。
ビジネスロジックとは、アプリのコアロジックを指す用語で、アプ
リが必要とする処理に特化したものを指します。例えば、誰かが製品
を購入するたびにメールを送信する必要がある場合、その製品がバー
モント州に配送される場合のみメールを送信し、カンザス州に配送され
る場合はテキストメッセージを送信します。これはビジネスロジック
です。
Railsの開発者がよく抱く最大の疑問は、「この種のロジックのコードは
どこに行くのか」ということです。Railsには明確な答えがありません。

ビジネスロジックをActive Recordsに置かないことです。その代わりに、
それぞれのロジックを独自のクラスに入れて、それらのクラスを
app/servicesやapp/businesslogicのようにapp/内のどこかに配置します
その理由は、道徳的な純粋さや、オブジェクト指向の設計原則に忠実で
あることとは関係がない。その理由は、ビジネスロジックで発見された
バグの影響を最小限に抑えることで、持続可能性に直接関係するもので
す。

kappazkappaz

Railsでコード書いてていつもすごく悩むのがこの辺り。
ビジネスロジックに該当するコードをどこに書くのか?
Controllerか?モデルか?はたまた別の何かか?
自分なりに整理したい系のスクラップ

kappazkappaz

調べないといけないこと

  • そもそもビジネスロジックとはなんぞや?
  • Rails的にはどのようにビジネスロジックを扱うのか?
  • で、個人的にはどんな手段が良さそうか?
kappazkappaz

そもそもビジネスロジックとはなんぞや?

kappazkappaz

ビジネスロジックとは、アプリのコアロジックを指す用語で、アプ
リが必要とする処理に特化したものを指します。例えば、誰かが製品
を購入するたびにメールを送信する必要がある場合、その製品がバー
モント州に配送される場合のみメールを送信し、カンザス州に配送され
る場合はテキストメッセージを送信します。これはビジネスロジック
です。

Sustainable Web Development with Ruby on Rails の 「5 Business Logic (Does Not
Go in Active Records)」より。これはまぁ分かりやすい。

ただRailsのモデルとの境界はどこなの?というのが難しい。

kappazkappaz

ActiveBusinessLogic::Base ク ラ ス を 継 承 す る こ と も 、 bin/rails
generate business-logicコマンドを呼び出すこともできません。

モデルに書けばいいんじゃね?的な話もあるがおそらくそれに対する著者の見解はこの辺り。少し苦しい面もある主張な気もするが、言いたいことはとてもよくわかる。

kappazkappaz

ビジネスロジックをActive Recordsに置かないことです。その代わりに、
それぞれのロジックを独自のクラスに入れて、それらのクラスを
app/servicesやapp/businesslogicのようにapp/内のどこかに配置します

その理由は、道徳的な純粋さや、オブジェクト指向の設計原則に忠実で
あることとは関係がない。その理由は、ビジネスロジックで発見された
バグの影響を最小限に抑えることで、持続可能性に直接関係するもので
す。

Sustainable Web Development with Ruby on Rails における対応方針はこちら。パッと見service objectを作れという話に見えるので、賛否両論ある話。実際この書籍の紹介記事でも大体この点が話題になっており、人によっては拒絶反応が出ている。
ただどうも先を読むといわゆる service objectとは違うらしい。また目的も「ビジネスロジックで発見されたバグの影響を最小限に抑えること」と非常に明確で実務的なので、service object が嫌いな人たちも一概には否定していないような雰囲気。

kappazkappaz

ビジネスロジックのコードは、コードベースの他の部分よりも複雑であり、安定性に欠けることを学びます。

この辺は単一責任とかあの辺りの話に紐づいている気がする。
ビジネスロジックは他と比べて複雑で変更される頻度が高いため、切り出しておきましょうというのが主張の核となる部分なように思う。特にコントローラーやモデルは色々な箇所から参照されるため、変更時の影響範囲も広く、脆いビジネスロジックを置く場所としては不適格という主張になるだろうか。その手段はおそらく何でもよくて、サービス(というかこの場合はPORO)が最もシンプルになるという帰結なのかな。

kappazkappaz

アプリ内で広く使用されているコードのバグが、孤立したコードのバグよりも深刻な影響を与える可能性があることを理解します。

まぁこういう話。

kappazkappaz

DDDの本でもドメインモデル(ビジネスロジックと対応する...かな?部分的には)はドメインへの理解や機能の追加に合わせて都度進化していくものとあった気がする。(意訳)

非常に変化していく可能性が高い箇所でもあるので、外からは切り出して孤立した存在にさせたいというのはとても分かりやすい話ないように思う。

コントローラーに入っている場合、
不具合が起きた際に、
コントローラーの本来の役割であるところの、パラメータの解釈やビジネスロジックのキックと、実際のビジネスロジックの中身のどちらに問題があるのか分かりにくかったり、ビジネスロジックの修正が「パラメータの解釈やビジネスロジックのキック」に影響を与えてしまったりする...のかな?

kappazkappaz

ビジネスロジックを実装するコードは、アプリの中で最も複雑なコード
の1つであることがわかっています。また、離脱率(修正の頻度的な意味合い)が高いことも分かっています。この2つの要因は、ビジネスロジックのコードにバグが発生しやすいことを意味することも知っています。

ビジネスロジックのコードはバグが発生しやすいので、孤立したクラスに閉じ込めたい。

kappazkappaz

トランザクションスクリプトパターン
トランザクションスクリプトパターンでは、「Service」クラスに処理を記述し、「DTO」をデータの入れ物としてやりとりします。
DTO はデータと getter、setter だけ持ち、処理は Service に記述します。
いわゆる手続き型プログラミングの考え方でビジネスロジックを実装する方式です。

やはりPofEAAは読まないとこの辺りの議論を理解できない気がする。

kappazkappaz

ビジネスロジックの実装方法は大きくこの 2 つになります。
作るアプリケーションの特徴やチームメンバのスキルなどを加味して、どちらのパターンで実装するかを決めることになると思います。

-> トランザクションスクリプト/ドメインモデル の2種類

https://ledsun.hatenablog.com/entry/2022/04/08/000748

レイヤーの認識はコントローラーとの近さからサービスオブジェクトをデータより遠く手続きに近いものだと認識します。 そしてサービスオブジェクトに「○○Service」という名前を与えます。 ひとたび「○○Service」という名前を得れば、それは手続きです。 サービス業が「もの」を売らないように、サービスが「もの」を持つはずがありません。

はこの トランザクションスクリプト/ドメインモデル の違いと関連しているように感じる。

要するに、
ビジネスロジックとして切り出した際に、
以降を手続的なものとして作るのではなく、
そこから先もきちんとオブジェクト指向のやり方に沿ってモデリングしましょうね、
という話だろうか。

kappazkappaz

ここまでビジネスロジックについて説明してきましたが、実はビジネスロジックは 2 種類あります。
エンタープライズビジネスルール
アプリケーションビジネスルール
の 2 つです。

この記事の前半で

ビジネスロジックには「じゃんけんの勝敗判定」のような「コアなルール系」と、「コンピュータとじゃ>> んけんをして、その結果をどこかに保存する処理を呼び出す、一連の流れ」のような「処理の流れ系」の 2 >> つがあります。
と書きましたが、前者が「エンタープライズビジネスルール」で、後者が「アプリケーションビジネスルール」に該当します。

この辺はDDDの本でもあったかな。
https://www.amazon.co.jp/dp/B082WXZVPC/ref=as_li_ss_tl?ie=UTF8&linkCode=sl1&tag=oshimayuki0d-22&linkId=e9cceb6c4bde218759a303625f687339&language=ja_JP

この本で言うところの、
ドメインサービスとアプリケーションサービスに該当するだろうか...?

kappazkappaz

「サービス」も 2 種類ある
ビジネスロジックが 2 種類あると書きましたが、ドメインモデルパターンにおいては、「サービス」も 2 種類あります。
アプリケーションサービスとドメインサービスです。

あるじゃん。

ドメインモデルパターンでは、エンタープライズビジネスルールをドメインモデルに記述し、アプリケーションビジネスルールを「アプリケーションサービス」クラスや「ユースケース」クラスに記述します。

あーね。

kappazkappaz

ドメインモデルパターンにおいては、「サービス」も 2 種類あります。

そうか、トランザクションスクリプトにおける「サービス」と、
ドメインモデルにおける「サービス」はそもそも違うと言う話になるな。
なんかそんな話聞いたような。

kappazkappaz

https://qiita.com/joker1007/items/25de535cd8bb2857a685

もし、trailblazerのOperationの様なコンテキストをハンドリングする立場としてのサービスレイヤを構築する場合は、自分達がどういう定義でサービスクラスというものを扱っているかを明確にして、そのルールに従うこと。
そうでないと、処理の記述場所が散らかって訳が分からなくなる。
前者のサービスと後者のサービスを混同するようなことは避けなければならない。

これは陥りそうだな...というか陥りつつある。
ルールを明確にしないと記述箇所がバラバラになり悲惨なことになると。

自分の作っているものが、前者のサービス(アプリケーションサービス?)なのか、後者のサービス(ドメインサービス)なのかは明確にできないとやる資格ない的な話かな。

kappazkappaz

サービスの名前の付け方全然うまくできてない...

kappazkappaz

もちろん、こういった事を考えてクラスを分けていくと、もしかしたら一連の機能的振舞いに対して名前が付いてクラスになることもあるかもしれない。
それはサービスクラスと呼べるものだが、モデルレイヤーの中の階層構造の中で表現する方が分かり易いと思う。変に別のディレクトリ構造に切り出すべきじゃない。
結局の所、それはドメインモデルが持つ大きな責任の中の一部で、オブジェクト同士の関係性の中に存在するものだと思うからだ。

ようやく何を言っているのか少し掴めてきた。
が...実現には大きな山があるなぁ...

kappazkappaz

ここまでの整理。(意訳も多い?)

ビジネスロジックとは

「システムのコアの部分」とか「システムの目的になる処理をするところ」

アプリケーションをプレゼンテーション・ビジネスロジック・データアクセスの 3 つに分けたとき、「プレゼンテーションでもデータアクセスでもない部分がビジネスロジック」
と考えるととっかかりやすいです。

https://qiita.com/os1ma/items/25725edfe3c2af93d735

ビジネスロジックの種類

ビジネスロジックにはドメインに関するコアな部分のロジックと、それを利用したアプリケーション上の手続的な部分のロジックの2種類がある。
(clean architecture 的に表現すると エンタープライズビジネスルールとアプリケーションビジネスルール)

実装方針

大まかに2種類

  • トランザクションスクリプト
    • 手続で表現(?)
  • ドメインモデル
    • オブジェクトで表現(?)

サービス

ビジネスロジックの実装方法として「サービス」を用いる。
ただしトランザクションスクリプトとドメインモデルで「サービス」の表す意味合いが異なる

トランザクションスクリプト

  • サービスクラスを用意しビジネスロジックを基本的に処理の流れとして作成する

ドメインモデル

  • 基本的にビジネスロジックをオブジェクト指向設計を用いてドメインモデルとして表現する
  • ドメインモデルはDDDで言うところのEntityやValueオブジェクトが中心になる。
  • ドメインモデルの中で、EntityやValueオブジェクトで表現しきれないもの(複数モデルにまたがる処理など)は、ドメインサービスとして実装する(ドメインモデルにおけるサービスの1つ目)。
  • 一方で ドメインモデルとしては不適切なアプリケーション固有の処理の流れに関しては、アプリケーションサービスとして作成する(ドメインモデルにおけるサービスの2つ目)。
    • DDD の戦術的設計では「アプリケーションサービス」と呼ばれ、クリーンアーキテクチャでは「ユースケース」と呼ばれています。
  • ということでドメインモデルにおいては、2つのサービスが存在する

ここまででよく参考にした記事

https://qiita.com/os1ma/items/25725edfe3c2af93d735
https://qiita.com/joker1007/items/25de535cd8bb2857a685
https://ledsun.hatenablog.com/entry/2022/04/08/000748

kappazkappaz

とりあえず、ビジネスロジックとはなんぞや?
に関しては一旦OKとしようか。

kappazkappaz

Rails的にはどのようにビジネスロジックを扱うのか?

kappazkappaz

5 Business Logic (Does Not Go in Active Records)

しかし、すべてのアプリの中で、Railsが明確な答え
を持っていない部分が1つあります。それは、ビジネスロジックです。

Railsの開発者がよく抱く最大の疑問は、「この種のロジックのコードは
どこに行くのか」ということです。Railsには明確な答えがありません。
ActiveBusinessLogic::Base ク ラ ス を 継 承 す る こ と も 、 bin/rails
generate business-logicコマンドを呼び出すこともできません。

kappazkappaz

13 Model Part 1

私の経験では、ビジネス・ロジックを別の場所に配置した場合、Active
Recordに多くのコードを必要とすることはありません。

kappazkappaz

ビジネスロジック以外では、Active Recordのインスタンスメソッドの最
も一般的な問題は、派生データ(その値がデータベース内のデータに基
づいている)に関係しています。この派生データは、ある時はプレゼン
テーショナルでユースケースに特化したものですが、別の時はモデルの
存在の核となる真のドメインコンセプトを表しています。

これ。これが個人的な悩みどころ。
あくまでプレゼンテーショナルなものとして考えた方が良いのか...?

悩んだ際は別クラス(モデル)に切り出す形を検討した方が良い気もする。

kappazkappaz

中断:
https://sustainable-rails.com/
の 14 データベース を読むところで止まっている。

sustainable web development...における
「Railsがビジネスロジックをどう扱っているか、どう扱うべきか?」
を自分なりに掬い取りたい。

kappazkappaz

14はあまり関係なさそうだったので飛ばす。

15 Business Logic Code is a Seam

この本の冒頭で、持続可能なRailsアーキテクチャの核心部分である「
Active Recordsにビジネスロジックを置かない」ということについて概説
しました。特に、53ページの「Active Recordsのビジネスロジックは重
要なクラスにチャーンと複雑さを押し付ける」というセクションで、そ
の理由を概説しました。

Sustainable... の主張としては、

  • ビジネスロジックは複雑になりやすくまた修正される頻度も高い
  • ActiveRecordは他から参照されることの多い重要なクラス
  • 重要なクラスに複雑さと修正頻度の高さが押しつけられる形になるので、ビジネスロジックはActiveRecordに置くべきではない

となるのかな。

kappazkappaz

ビジネスロジックを実装するコードは、アプリの中で最も重要です。な
ぜなら、アプリが提供する結果を実現するからです。また、繰り返し実
装され、変更に対応しなければならないため、最も安定性に欠けるコー
ドでもあります。そのため、このコードは、動作することはもちろん、
理解しやすいものでなければなりません。なぜなら、コードを変更する
ためには、コードを理解することが必要だからです。

これはDDDの考えとも共通する話ですね。
最低でもエンジニアから見てパッと見で何してるかわからないビジネスロジックはとても危うい。

kappazkappaz

私は、開発者が上記の目的の1つ以上を果たすために、動作の明確さを犠
牲にしてコードを書くのを何度も何度も目にしてきたので、このことを
述べます。コードをリファクタリングして「よりOOに近づける」ことは
、まやかしの行為です。特に、SOLID原則と呼ばれるものは、広範囲に
適用するとコードベースに大打撃を与える可能性があります。1.私は、
これまでのキャリアで、何度もこの罪を犯してきました。

動作が明らかでわかりやすい方が良い。原則に則るために動作の明確さを犠牲にしてはダメよと。

kappazkappaz

私が最も成功し、また他の人も成功しているテクニックは、単一のクラ
スとメソッドを作成し、そこから特定のビジネスロジックを開始するこ
とです。このクラスとメソッド(およびメソッドが返すオブジェクト)
は、Railsで管理される一般的なコードと、私自身のコードの間の継ぎ目
、あるいは境界線となります。そのクラスの内部は、必要に応じて自由
に構造化することができます。