はじめに
こんにちは、ITANDI株式会社でエンジニアをしているたのもきです。
9月26日〜27日に行われた Kaigi on Rails 2025 に参加したので、参加レポートを書きました。 今年行われたRuby Kaigiに続き初参加だったのでソワソワしながら臨みました。

セッションはHall Red、Hall Blueに別れて毎時間同時開催。毎回どちらのセッションに参加するか非常に悩むくらいどのセッションも魅力的な表題でした。できることなら影分身して両方のセッションを聴いてたかったです笑
その中でも印象に残ったセッションを今回はいくつか紹介したいと思います!
【セッションレポート】今改めてServiceクラスについて考える
1. セッション概要
本セッションは、名著『パーフェクトRails』でServiceクラスの章を自ら執筆した登壇者が、約10年が経過した今、その功罪を改めて問い直すものでした。
結論として、Serviceという名前があまりにも曖昧であり、チーム開発において明確な共通認識を作るのが困難であるため、「安易に使うべきではない」という見解が示されました。
Serviceクラスで解決しようとしていた「ファットモデル問題」は、結局Active Recordへの依存関係の難しさから逃れられていないと指摘。むしろ、用途が明確な「フォームオブジェクト(Form Object)」の方が、スコープが限定されており、現実的な設計パターンとして優れていると提唱されました。
最終的に、コンポーネントの境界を明確にする「モジュラーモノリス」(Shopifyのpacksなど)が今後の主流な解決策になり得るとし、設計に「正解はない」ため、チームで継続的に議論し続けることの重要性が説かれました。
2. 主要トピックと技術的ハイライト
Serviceクラスの問題点:「共通認識」の欠如
曖昧な名前:
Serviceという言葉は万能すぎて、逆に「何を入れていいか」の基準をチームで揃えるのが非常に難しい。逃れられない依存:
Serviceクラスを作っても、結局はActive Recordモデルを操作するため、モデル側の修正がServiceクラスに影響する「密な依存関係」から逃れられていない「想像がつく」ことの重要性: 重要なのは、チームメンバーがコードの構造を「想像がつく」(予測可能である)こと。
Serviceクラスはこの「想像のしやすさ」を阻害することがある
フォームオブジェクト(Form Object)の再評価
Serviceクラスの代替として、より責務が明確なフォームオブジェクトが推奨されました。
明確な責務: フォームオブジェクトの責務は「特定のフォームからのパラメータを受け取り、バリデーションし、トランザクションを管理する」ことのみに限定される
名前が実態を表す:
Formという名前自体が「Webフォーム」という具体的なイメージと直結するため、チーム内の共通認識が作りやすい
真の解決策:「モジュラーモノリス」と「境界」
登壇者は、真の解決策は「境界(Boundary)」を明確に引くことにあると述べました。
DDDとコンテキストマップ: DDD(ドメイン駆動設計)の「境界づけられたコンテキスト」の考え方を引用し、システムを機能やドメインで分割し、その「境界」を管理することが重要
packsによる可視性のコントロール: Shopifyが開発したpacks(packwerk)のようなツールは、モジュール間の依存関係や可視性を強制的にコントロールできるServiceクラスの新たな可能性: モジュール同士が連携する際、その「公開API(エントリーポイント)」としてServiceクラスを定義するのは、明確な役割があるため有効かもしれない、という新しい使い方が示唆された
3. 得られた学びと実践したいこと
Serviceクラス導入の慎重化: チーム内で明確な合意がない限り、安易にServiceディレクトリやクラスを作成するのは避けたいと思います。フォームオブジェクトの積極的活用: 複雑なフォーム送信や、複数のモデルを一度に更新するトランザクション処理には、
Serviceクラスの代わりに「フォームオブジェクト」パターンを第一候補として検討して行こうと思いました。「想像しやすいコード」を意識: Serviceクラス、フォームオブジェクト等はあくまで手段の一つでしかない。設計の良し悪しを「チームメンバーがその構造を容易に想像できるか」という基準で判断する文化を醸成する。
RDBと
Active Recordの基礎に立ち返る: 一番の学びというか重要だと再確認したのはこちらです。Serviceクラスのような応用パターンに飛びつく前に、RDBの正規化やActive Recordのコールバック、バリデーションを適切に使いこなす基礎的な設計力こそが重要であると改めて感じました。
【セッションレポート】Railsによる人工的設計入門
1. セッション概要
本セッションは、「設計」という抽象的なスキルをいかにして学び、教えるかという課題に対する、具体的な手法を提案するものでした。
登壇者の大場氏は、初心者が「システム=コード」と捉えてしまい、「設計」をしようとすると頭の中ですべてのコードを実装しようと(「脳内実装」)してしまい、結果として設計活動そのものが失敗する、という問題を指摘しました。
この課題に対し、「人工的設計(Artificial Design)」という手法を提案。これは、実装(コード)から考える「順算」ではなく、「完成したシステム」から考える「逆算」によって、設計活動を強制的に引き起こすというアプローチです。この手法により、抽象的な設計プロセスを具体的なステップに落とし込み、誰もが設計を実践できるようにすることを目指します。
2. なぜ設計は難しいのか(初心者のつまずき)
登壇者が指摘した、設計の学習が失敗するメカニズムは以下の通りです。
「設計しよう」が伝わらない:
- ベテランは「システムを抽象度の高いレベルで考える」ことを「設計」と呼ぶ。
- 初心者は「システム=コード」と捉えているため、「設計」と言われると「コードを書く前の脳内実装」を始めてしまい、抽象レベルでの思考ができない。
「手順」と「設計」の混同:
- 初心者は「DB→モデル→コントローラー」といった「実装の手順」を設計と勘違いしがち。
- その結果、システム全体を俯瞰する設計が行われず、手戻りが多くなる。
3. 「逆算」による人工的設計の手法
セッションでは、「CSVユーザーインポート機能」を例に、逆算による設計プロセスが実演されました。
Step 1: 完成したシステムを思い浮かべる
まずコードではなく、「完成したUI」(ユーザーが触る画面)をイメージする。 * 例:CSVのアップロード画面、インポート成功時のメッセージ。
Step 2: 「気になること」を洗い出す
そのUIを見て「気になること」(懸念点)を洗い出す。これが「重要な技術的要素」の発見につながる。 * 例:「大量のユーザーだと時間がかかりそう」→ 非同期処理が必要 * 例:「エラーはどうなる?」→ エラー表示が必要 * 例:「誰がいつインポートしたかわかる?」→ 履歴モデルが必要
Step 3: 「ゴール」を明確にする
システムが実現したい「最も本質的なこと(ゴール)」を一つ見定める。 * 例:「CSVからユーザーを登録・更新する」という非同期処理そのもの。
Step 4: 「逆算」で問いを立てる
ゴールから逆算して、「それを実現するために必要なもの」を問いで繋げていく。
* 問い1: 「ゴール(非同期処理)」を実現するには何が必要か?
* → 答え: UserImportJobと、それが実行する「インポート処理」ロジック。
* 問い2: 「インポート処理」ロジックには何が必要か?
* → 答え: 処理の対象となるImportモデル(インポート履歴)。
* 問い3: Importモデルを作るには何が必要か?
* → 答え: ユーザーがファイルをアップロードする最初の画面(Step 1の画面)。
このプロセスを経ることで、コードの詳細(実装)からではなく、システムの全体像(アーキテクチャ)から考えることができ、これが「設計」活動そのものになります。
4. 得られた学びと実践したいこと
- 設計教育への導入: 新しいメンバーやジュニア開発者に設計を教える際、「まず設計して」と抽象的に指示するのではなく、「まず完成画面をイメージして、そこから逆算してみよう」という具体的なステップを提示することで、設計思考を「人工的」に誘導できる。 タスクを振られた際、どのようなコードを書けばいいのだろうと思いがちなのは凄く共感できたので、コード画面のイメージから入るのは非常に重要だと感じました。
【セッションレポート】そのpreload必要?
1. セッション概要
本セッションは、登壇者のMugi氏が所属する株式会社ギフティで実際に発生した、深刻な本番障害のケーススタディでした。
ある日突然、特定のエンドポイントで500エラーが多発し、サーバーが次々とダウン。調査の結果、原因はたった1行のpreloadが400MB以上のメモリを消費し、OOM (Out Of Memory) を引き起こしていたことでした。
さらに深刻だったのは、そのpreloadが「現在はViewで全く使われていない、不要なコード」だった点です。過去の仕様変更で不要になったpreloadが放置され、プロダクトの成長によるデータ量増加に伴い、静かにメモリ消費量を増やし続け、最終的に障害を引き起こした「技術的負債」の実例として共有されました。
2. 障害の根本原因と技術的背景
preloadが引き起こしたメモリ肥大化
- N+1の解決策、その代償:
preloadはN+1クエリ問題を解決する強力な手段ですが、その裏で大量のActive Recordオブジェクトをメモリ上に展開するというコストが発生します。 - ネストによる爆発:
特に
includes(comments: :likes)のようなネストしたpreloadは、オブジェクト数が掛け算で増加します(例: 10記事 × 100コメント × 100いいね = 10万オブジェクト)。今回の障害は、これが400MB超のメモリ消費につながっていました。
「不要な preload」という技術的負債
- 障害の引き金:
この
preloadは、過去のUIでは必要でしたが、その後の改修で画面表示に使われなくなっていました。 - 「静かな時限爆弾」:
不要になった
preloadを削除しなかったため、コード上は残り続けました。プロダクトが成長し、関連データ(コメントや「いいね」)が増えるにつれて、このpreloadが読み込むオブジェクト数が静かに増加し、ついにサーバーのメモリ上限(1GB)を超える「時限爆弾」となりました。
なぜ防げなかったのか
- 監視不足: メモリ使用率のメトリクス(New Relicなど)は取得していたものの、閾値(しきい値)を超えた際のアラート設定がなかった。「見ている」だけで「監視」していなかったため、予兆を検知できませんでした。
- 共通認識の欠如:
チーム内で
preloadを「N+1を解決する銀の弾丸」のように捉えており、そのメモリコスト(トレードオフ)に対する共通認識が不足していました。
3. 得られた学びと実践したいこと
bulletGemの活用:bulletGemを導入し、「N+1クエリ」だけでなく「未使用のEager Loading(preload)」も検知するよう設定する。これにより、今回のような「不要なpreload」を開発・CI段階で自動的に発見できます。 N+1はSentryで検知しているのでbullet導入しなくてもいいと思っていたのですが、未使用のpreloadは検知できていなかったので、これを補完する形で導入したいと思いました。- 「見直す習慣」の文化付け:
ViewやAPIのレスポンスを変更する際は、必ずコントローラー側の
preloadも見直すというレビュー習慣を徹底していきたいと思いました。
終わりに
初参加でしたが非常に刺激的な2日間でした。 Kaigi on Railsは、Ruby Kaigiと比べてより実践的で技術的な内容が多く、日々の開発に直接役立つ知見が多かったと感じます。
次回は渋谷開催とのことで、また参加したいと思います!