Slide 1

Slide 1 text

今改めてServiceクラスについて考える 〜あるRails開発者の10年〜 joker1007 (Repro株式会社) Kaigi on Rails 2025 1

Slide 2

Slide 2 text

自己紹介 Tomohiro Hashidate id: joker1007 Repro株式会社 チーフアーキテクト 元々はRailsエンジニアだったが、最近はJavaばかり書いている スティール・ボール・ランのアニメ化が楽しみで生きている パーフェクトRuby及びパーフェクトRuby on Railsの著者の一人 2

Slide 3

Slide 3 text

Reproから来ました 日々大量のデータを扱い、デジタルマーケティングを支援してます。 3

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

パーフェクトRuby on Railsの第1版 2014/6/6 発売 11年前、ざっくり10年以上経った。 (思えば遠くに来たもんだ) 5

Slide 6

Slide 6 text

9章の6 サービスクラス ここを書いたのが俺。 6

Slide 7

Slide 7 text

そしてもう一つ 「俺が悪かった。素直に間違いを認めるから、もうサ ービスクラスとか作るのは止めてくれ」 at Qiita https://qiita.com/joker1007/items/25de535cd8bb2857a685 これを書いたのが2016年末。パRailsを書いてから2年半後ぐらい。 2年半で言ってること変わってんじゃん!と言いたくなるかもしれない。自分でもそう思う。 7

Slide 8

Slide 8 text

最初のパRailsから大体10年後のこと…… 8

Slide 9

Slide 9 text

2024/09/07 福岡Rubyist会議04 懇親会にて @junk0612 「サービスクラスってどう思います?」 @joker1007, @onk 「オッ!サービスクラスの話?」 みたいな感じになりまして、 ちょうど近くに座っていた @snoozer05 さんとビアバーに流れて議論する機会があった。 これを機にちょっと話をしたいなーと思っていたら、この前の関西Ruby会議08でもサービス クラスへの言及があって、話すなら今だなと思った。 9

Slide 10

Slide 10 text

この時は、実際の仕事の現場では出来る限り使わない 方が良い、という意見で概ね合意があったと思う (それなりに飲んでたので記憶違いだったらごめんなさい。 訂正歓迎。 ) 10

Slide 11

Slide 11 text

何故使わない方がいいのか 「開発統制の困難さを上回るメリットが得られない」 もう少し具体的な話をする前に過去を振り返っておきたい。 ちなみに、ここから文字がめっちゃ多いです。 1日の最後に眠い話になるかもしれませんが、お付き合いください。 11

Slide 12

Slide 12 text

そもそもサービスクラスとは何だったのか 当時俺はどの様に書いていたのか 過去の文献や資料、そして自分が書いた文章を読み返 してみた 12

Slide 13

Slide 13 text

2011年4月 エリック・エヴァンスのドメイン駆動設計 発売 (原著は2003年発売) 日本でサービスクラスという名前が語られることが増えた背景に、DDD本の影響があったこと は無視できない。 この発表にも内容への言及がいくつか出てくるが、DDDという手法を採用する・しないに関わ らず、自分達が解決したい業務ドメインに対する理解を深めることは、どんなソフトウェアを 開発する上でも重要な営みだと考えているため。 Railsのバージョンとしては3.1〜3.2ぐらいの時期で、ActiveRecordが今も残る形に変更さ れ、Sprocketsが入り、多くのスタートアップで使われる様になってきた時代。 ちょうど自分もRailsで仕事が出来る様になった頃で、個人的には一番熱かったかも。 13

Slide 14

Slide 14 text

Fat Modelに対する問題意識 Railsがビジネスの現場で使われることが多くなった結果、あちこちでFat Modelが問題として 顕在化し始めた。 ActiveRecordの特性上、システム自体が複雑になると色々な箇所から参照される基盤となる クラスに多くの処理が記述され、複雑度が爆発し依存関係がとっ散らかって管理しきれなくな る、という問題がしばしば発生する。 それに対処するために、モデルを整理するテクニックがあちこちで語られる様になりました。 Rails fat modelでググると2010年頃からそういうテクニックについて語られている記事が増 えているのが分かります。 この頃からサービスクラスについての言及も多く出てきます。 14

Slide 15

Slide 15 text

DDD本におけるサービスとは (要約) ソフトウェアのビジネスドメインから生まれる概念の中で、エンティティや値オブジェクトに 責務を持たせるとそれらの定義を歪めたり不自然なオブジェクトが生まれる場合がある。そう いった時に振舞いに着目して何が実行できるかという観点から命名し責務を定義するもの。 そして、その命名と操作名はユビキタス言語に由来していなければならない。 ユビキタス言語についての簡単な説明: ビジネスドメインについて議論・設計を行う際に実際 の業務で表現される語彙とコード上の語彙を統一して、ドメイン理解とコード上の表現の相互 のやり取りの基盤となるもの。我々が何を作ろうとしているのかを突き詰めていく際のコミュ ニケーションの基盤となるもの。 15

Slide 16

Slide 16 text

パーフェクトRailsにおけるサービスクラス (要約) コントローラーとモデルの中間に立つもので、コントローラーはHTTPリクエストをハンドリ ングするインターフェースであり、サービスはモデルが行う処理を取り纏めるインターフェー スである。 サービスクラスは処理そのものをカプセル化したものであり、かつ業務知識と実装のメンタル モデルを一致させることが重要な役割の一つである。 業務知識と照らし合わせて自然な形でアプリケーションを設計するための手段の一つ。 適切な名前付けが出来ないならサービスクラスを利用する対象として不適切な可能性がある。 16

Slide 17

Slide 17 text

意外とちゃんと書いてある (自分相手だと、いくらでも上から目線になれますね ) 乱用は危ないから考えて使おうね、とも書いてある。 実際書いた時もDDD本は大いに参考にしたし、先達の記事も一杯読んだ。 余談だが、パーフェクトRailsはこういう現場でコードを書いている目線からの知見を大事に したいと思って書いていた。 しかし……。 17

Slide 18

Slide 18 text

この時に分かってなかったこと 「乱用は危ないから考えてから使う」の度合いが人によってバラバラだというこ と ビジネスドメインの語彙に対する理解度が人によってバラバラだということ 単にややこしい処理に安易に名前付けて良い訳じゃないってこと ある機能の振舞いとActiveRecordのデータ操作を適切に分離することの難しさ 自社開発の会社で役職を得て人の開発を監督する責務を負う様になり、ちょっとづつ分かって きた。 18

Slide 19

Slide 19 text

チーム開発における共通認識の重要さ Railsを活用した事業が増え、2、3人のすぐに話が伝わるレベルでは収まらない開発が普通に なった。自分もゼロからのスタート時点で関わっていない仕事が増えた。こういう世界で継続 的に開発のペースを維持するためには、チーム全体の水準と共通認識が重要になる。 一貫性を伴ってコードを書いてないと、少し時間が経っただけで何がどうなってるかすぐ分か らなくなる。 人はすぐに物事を忘れるし、自分が書いたコードがいつの間にか謎の拡張をされていたりする のは日常茶飯事なので、大事なのは「ちゃんと想像が付く」こと。 19

Slide 20

Slide 20 text

想像が付くとはどういうことか 機能のエントリポイントから戻り値を返すところまでの全体像を把握しやすいこ と どのディレクトリに何が入っているか分かること クラス名・メソッド名で処理内容がイメージできて、副作用の有無が分かること ソースコードそのものに対しては「リーダブルコード」という良い本がある。 これを構造レベルで実践すると考えて欲しい。 20

Slide 21

Slide 21 text

サービスクラスの基準の作りにくさ 一つのモデルに収まらない複雑な処理を表す振舞いに考えて名前を付けろ、では現実的に基準 にならない。 想像が付く様にするためには、もっと明確で覚え易いルールが必要になる。 単純なルールを決めたとして、本当に常にそうあるべきか?仮にそうだとして、裏側に ActiveRecordのモデルが居る訳で、モデルに書く処理とサービスクラスに書く処理のメソッ ドレベルでの境界はどこにあるのか?その書き方の理由を説明が出来るか? 朝のキーノートでメール送信コードどこに書く?というテーマでmoroさんが考え方を示して くれましたが、そもそもコードの配置を適切に行う時点でかなり考える力が要る。 21

Slide 22

Slide 22 text

Railsのレイヤー分類はよく出来ている Railsの基本的なレイヤーはController, Model, Viewだけだ。Jobは非同期処理向けの Controllerと言える。 Controller: ユーザーからのリクエストをハンドリングし、モデルの処理結果を表 示するviewを選択する Model: RDB操作とビジネスロジック全部 View: 処理結果の表示 これをきっちり守るだけで大体どこに何があるか想像が付く。 人間が簡単に覚えて共通認識を作れるのはこの程度だという割り切りが見える。 こういう共通認識の力こそがフレームワークを活用する利点。 逆に言えば共通認識を破壊するとフレームワークの利点が失われる。 22

Slide 23

Slide 23 text

サービスクラスを使っても問題ないケース 一人、または少人数で開発していてすぐにコミュニケーションが取れる チームの大半が対象業務を十分に理解しており、読み易いコードを維持すること についての水準が高い こういう場合なら使っても問題ないし、見通しが良くなるケースもあると思う。個人開発なら 基本的に問題ない。 最低でも一人はここまで話した様なことが自分で説明できて、コードレビューに時間が割ける ことが必要。 今日のトークの入門FormObject冒頭でFormObject説明できる人~って聞いてて、あまり手が上がらなかったですね。 この説 明の難しさがチームで書くコードのクオリティを保つ難しさでもある。 23

Slide 24

Slide 24 text

じゃあ、Fat Modelはそのままで良いのか? もちろんそんな訳はない 24

Slide 25

Slide 25 text

Formクラスってどうなん? 25

Slide 26

Slide 26 text

現実的な折衷案としてのFormクラス ServiceクラスとFormクラスはActiveRecordの単体クラスの責務とするのが難しい問題の置 き所としては同じものだが、扱う文脈の限定度合いが違う。 Formクラスの方がより限定的な問題に対処するもので、それが名前にも表れている。 だからこそ、現実的な折衷案として機能する。 26

Slide 27

Slide 27 text

Formクラスの責務 (これも諸説あると思うが……) Webアプリケーションにおける重要な処理の大半は、HTMLフォームもしくはそれに準ずるイ ンターフェースによってまとめられたパラメーターのPOST/PUTによって発生する。 つまり、パラメーターハンドリングとトランザクションコントロールを扱うレイヤーがあれ ば、大半の複雑な問題を扱える、そういう割り切りの元に存在している。 昨今のWebアプリケーションは入力パラメーター自体がそもそも複雑なので、ここがなんとか なれば良い。 27

Slide 28

Slide 28 text

サービスクラスとの違い Formクラスは画面やユースケース(つまり大体アクション)と紐付いている ActiveModel, ActiveRecordのインターフェースと親和性が高い Formという名前から想像できる具体的なイメージがある 元々はビューに表示するformヘルパーとの相性も考慮されていたが、昨今はJSで 組み立てることが多いので、この点は重要ではなくなってきた。 単なる名前と印象の問題では?と思うかもしれないが、業務としてのソフトウェア開発におい てそれは物凄く重要である。チームメンバーが納得できる名前を付けられるかどうかがその後 の開発のしやすさに大きな影響を与える。 入門FormObjectで話をされていたのは機能的な分類だったが、自分は名前とそこから発生す る共通認識に注目している。 28

Slide 29

Slide 29 text

Formクラス、サービスクラス共通の難しさ 複数のattributeに関する操作やRDBに関する操作をまとめて、ActiveRecordのクラスのメソ ッドに定義したとする。 これを複数のForm、Serviceから再利用しようとすると、ActiveRecordのメソッド側から修 正したくなった時に困るし、Form側からの要請でメソッドを弄って大丈夫か怪しい、みたい なことになる。 結局ActiveRecordを上手く整理することから逃げられない。 ある意味ではRailsの大きなしがらみでもある。 JC版 ダイの大冒険22巻より 29

Slide 30

Slide 30 text

結局のところ、ServiceだのFormだのは余り重要では ない Railsの世界で言えば全部モデルの世界をどう整理するかという話で、わざわざ別のディレク トリ区切る程なのか? ちゃんとシンプルさを求めることから逃げてないか? 30

Slide 31

Slide 31 text

まずRDBと向き合う 参考: Identifying User Identity by moro dynamic! by moro パーフェクトRails著者が解説するdeviseの現代的なユーザー認証のモデル構成に ついて by 俺 31

Slide 32

Slide 32 text

RDBの基本 自然に表現できて安全なデータ構造を見出すこと エンティティとイベントの区別 中間表現に適切な名前を付ける 正規化・SQLの表現テクニックを学ぶ 状態ではなく事実の有無を活用し、データベースの制約を活かす設計 テーブルやカラムにメンバーが自然に呼称できる適切な名前を付ける 32

Slide 33

Slide 33 text

まずActiveRecord、ActiveModel、SQLを使いこな す 外から呼ばれるメソッドか、Validationか、Callbackかを適切に選択する Validation, Callbackに分岐を発生させない テーブル設計と組み合わせて状態管理をシンプルに ユニーク制約に影響を与える削除フラグを止めるとか STI, polymorphicは難しい、気を付けろ ちゃんとscope作って名前を付ける joins, includes, preloadの違いを理解し、EXPLAINはすぐ確認できる様に 33

Slide 34

Slide 34 text

我々が本当に整理したかったこと 34

Slide 35

Slide 35 text

重要なのはドメインコンテキストの理解 複雑なシステムを設計する時に重要なのはコンテキストマップを作ること。DDDにおける基本 にして奥義だと思う。 DDD本においては境界づけられたコンテキストと呼ばれる。 そして、その一連のコンテキストがシステム内部のどのコンポーネントに相当し、どうやって 他のコンテキストと相互作用するのかを考える。 コンテキストをコードを中で自然に表現できて、コンテキスト同士は境界を越えた先に影響を 与えない様にコントロールしたい。 35

Slide 36

Slide 36 text

境界づけられたコンテキストとは 同じユビキタス言語(語彙)が使える業務領域のこと。 例えば、Reproではスマホに対してプッシュメッセージを送ってマーケティングコンテンツを 配信することと、アプリケーション内のポップアップでマーケティングコンテンツを配信する ことは、同じ「コンテンツ配信」だが実態としては、前者は外部の配信APIへのリクエスト送 信、後者は自社のAPIに対するSDKからのリクエスト受信、とやりたいことが真逆だし、期待 する配信タイミングも目的も異なる。 同じ言葉を使っても、意味や期待する動きが異なるなら同じコンテキストとして扱うのが適切 ではない可能性がある。 36

Slide 37

Slide 37 text

依存関係を一方向に保つ 機能のワークフローを描く時、集約のルートとなる場所が明確になる様にデザインし、そこか ら依存関係の方向が逆戻りしない様に設計する。 Rubyの世界では古くはmoduleを利用した名前空間とディレクトリ構成だったが、現代にお ける現実的な選択肢はやはりモジュラーモノリスになるだろう。 37

Slide 38

Slide 38 text

モジュラーモノリスのやりたいこと 分散システムの複雑性から可能な限り距離を取りつつ、コンテキストの境界を明 確にし強制する 認知負荷を下げる 開発の統制を保つ 但しRubyには同一のコードベース内で可視性を強制する手段が無かった。 なのでShopifyはPackwerkを作った。Zeitwerkを利用して定数参照にフックをかける形でコ ード間の依存性を検証する。 38

Slide 39

Slide 39 text

現実的だが楽な道ではない 参考: A Packwerk Retrospective (2024) - Shopify Packwerkの依存性検出には限界があるし、コンポーネントの責務をどう分離するかの示唆を 与えてくれる訳ではない。 特にドメインで分離した結果、機能面での依存が不自然になるのは非常に難しい問題で、簡単 に結論が出せる様な問題ではない。 コンテキストマッピングもコンポーネントの分割も、一度で上手くいく様なものではないの で、継続的に何度も判断を下し続けてコードとメンタルモデルを相互に改良し続ける必要があ る。(こういう行いをドメインモデリングの文脈では蒸留と呼ぶ)。 39

Slide 40

Slide 40 text

Serviceクラスの復活 モジュラーモノリスによってコンポーネントの境界が明確になった時、Webというインターフ ェースに限定されないコンポーネントレベルでの公開エントリポイントが必要になる。 ここでServiceクラス(またはそれに類する何か)を利用するのは意味があると考えている。こ の時、ドキュメント化された呼び出し規約は絶対に必要になる。 マイクロサービスにおいてはリポジトリで管理されるスキーマなどがそれに当たる。モジュラ ーモノリスではメソッドの型になるだろう。 型検査をやるならこういったコンポーネントの境界を跨ぐ公開エントリポイントが最も重要度 が高い。 40

Slide 41

Slide 41 text

新しい世界 Ruby::Box (by @tagomoris) 今開発が進んでいるNamespace改めRuby::Boxによりもたらされる可視性のコントロールは Packwerkより遥かに強力。 Ruby::Boxではある名前空間のクラス定義構造は他のものと完全に独立している。定義自体が 無いものはどうやっても呼べない。 流石に早々に使える様にはならないと思うが、より自由なディレクトリ構成を持ちつつコンテ キスト境界を明示する一つの手段になるのではないかと期待している。 41

Slide 42

Slide 42 text

実は言いたかったこと チームで継続的に仕事として開発することの難しさ、そしてそれへの向き合い方。 Serviceクラス、Formクラス自体は合理的な場合もあるが上手く活用することは 難しい。特にチーム開発では。 もし使うならチームメンバー全員でどういうケースで使うのか、どうして必要な のかをちゃんと言語化し話し合う。 重要なのは、開発者にとって想像が付くこと、驚きが少ない開発。読めるコー ド、そして読める構造へ。 ServiceクラスやFormクラスはコード上の表現技法の一つに過ぎない。コンテキ ストマッピングをしっかり考えて、継続的に改善をし続けること。 42

Slide 43

Slide 43 text

正解は無い、一緒に考え続けましょう ソフトウェア設計は継続的な営み。 ITシステムの設計ってのは、ある瞬間に適切な選択をすることではなくて、継続的に開 発が滞りなく行えて新しい関係者が物事を理解しやすい状態を維持し、ビジネスが意図 した通りに発展できる状態を維持し続けることだと伝えていきたい。 — joker1007 (アルフォートおじさん) (@joker1007) June 18, 2025 43

Slide 44

Slide 44 text

We're hiring!! 一緒にソフトウェア設計を考えてくれる仲間を探しています。 よろしくお願いします。 44