Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
torikago - Ruby::Boxで照らすモジュラモノリスの実行境界
Search
nori
July 19, 2026
Programming
390
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
torikago - Ruby::Boxで照らすモジュラモノリスの実行境界
nori
July 19, 2026
Other Decks in Programming
See All in Programming
メールのエイリアス機能を履き違えない
isshinfunada
0
240
使いながら育てる Claude Code — 開発フローの1コマンド化 × 繰り返し指摘の自動仕組み化
shiki_kakaku
0
1.8k
自動化したのに回らないテスト運用の壁ーAI時代の品質責任と生産性
mfunaki
1
140
Claude Code全社展開のためにやったことn選~プラグイン302個・コミッター271人を支えるために~
kenchan
5
1.6k
人間の目はかわらない、だからJPEGは30年もつ
yuzneri
12
19k
5分で問診!Composer セキュリティ健康診断
codmoninc
0
1k
Google Apps Script で Ruby を動かす
kawahara
0
270
freeeにおけるEvalsの実践例の紹介
freee
PRO
0
120
改善しないと、タスクが回らない。 “てんこ盛りポジション” を引き継いだ情シスの、入社3ヶ月の業務改善録
krm963
0
270
属人化した知識を、 AIが辿れる地図にする
pkshadeck
PRO
1
190
【QA Test Talk Vol.8】AI-DLC による Whole Team Approach の加速
pkshadeck
PRO
0
200
楽しそうなつよつよエンジニアと目が死んでる僕/A brilliant engineer having a blast, and dead-eyed me.
3l4l5
2
190
Featured
See All Featured
Darren the Foodie - Storyboard
khoart
PRO
3
3.7k
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Code Review Best Practice
trishagee
74
20k
Designing for Timeless Needs
cassininazir
1
440
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
Building an army of robots
kneath
306
46k
Git: the NoSQL Database
bkeepers
PRO
432
67k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
4.4k
Unsuck your backbone
ammeep
672
58k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
800
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
800
Transcript
torikago Ruby::Boxで照らす モジュラモノリスの実行境界 nori (@se4weed)
分離するのが 最も一般的な方法です。 自己紹介 nori (21) se4weedというIDをよく使っています SmartHRの2025年新卒 阪神タイガースと酒とゴルフが好きです
Railsで困ったことありませんか?
Railsアプリケーションの悩み アプリケーションが大きくなるにつれて、依存関係が複雑になる 「この変更はどこに影響するのか」が分からず、コードを触りづらい 安全に開発を続けるために、依存関係を整理したい
モジュラモノリスという考え方 ドメインごとにモジュールを分割する モジュール間の依存関係を明確にする モジュール外にはPackage APIだけを公開する
モジュラモノリスを実現する仕組み Packwerk YAMLで定義したモジュール間の依存関係をもとにコードを静的解析する 例: Package API以外への参照をルールとして検査できる Rails::Engine controllers、models、routesなどを名前空間やパッケージとして整理する仕 組み
モジュラモノリスを実現する仕組み モジュール間の構造上の境界を作れる モジュール間の依存関係を明示できる
しかし…
構造上の境界と実行時の境界の不一致 モジュールを分けても、実行時には同じRuby VMで動いている 他のモジュールの定数などを実行時には直接参照できる モンキーパッチなどの副作用がモジュール境界を越えて広がる
マイクロサービスにすれば解決するのでしょうか?
マイクロサービスなら 変更の影響範囲をサービス内に閉じ込めやすい サービス単位で実行環境が分離される 他サービスの実装に直接依存できない
だがしかし!
マイクロサービスなら モジュール間の呼び出しがサービス間通信になる データベースもサービス単位に分けることになる 1つのトランザクションで完結しなくなる
モジュラモノリスか。マイクロサービスか。 それ以外の選択肢はないのでしょうか?
発想を変えてみる マイクロサービスにするのではなく、モジュラモノリスのまま実行時の境 界を作れないだろうか
そこで、Ruby::Box📦
Ruby::Boxとは Ruby::Boxは、1つのRuby VMの中で実行空間を分けるための実験的機能 Box内で読み込んだクラス / モジュール定義やmonkey patch、ライブラ リのロード状態などを、他のBoxから隔離できる
Ruby::Boxでモジュラモノリスの 実行環境を分離できないだろうか?
思い出すシリーズ 引用: @joker1007「Do Ruby::Box dream of Modular Monolith?」RubyKaigi 2026 LT
torikago
torikagoとは モジュールのController、Model、private class、Package APIなどを Ruby::Box内で実行するgem Ruby::Boxを使って、モジュラモノリスに実行時の境界を作る
用語 Rootモジュール モジュールとして隔離しない、app 直下のModel, Controllerなどの総称 Package API 他のモジュールからの呼び出しを 許可するクラス・メソッド 📁
Project/ ├── 📁 app/ │ ├── 📁 controllers(root module)/ │ │ └── 📄 application_controller.rb │ └── 📁 models(root module)/ │ └── 📄 customer.rb ├── 📁 modules/ │ ├── 📁 bar/ │ │ └── 📁 app(bar module)/ │ │ ├── 📁 models/ │ │ │ └── 📄 product.rb │ │ └── 📁 package_api/ │ │ └── 📁 bar/ │ │ └── 📄 products_query.rb │ └── 📁 foo/ │ └── 📁 app(foo module)/ │ └── 📁 controllers/ │ └── 📁 foo/ │ └── 📄 dashboards_controller.rb └── 📁 queries/ └── 📄 customers_query.rb
モジュールをRuby::Boxで隔離する
モジュールごとにBoxを作る このようにDSLで定義することで、torikagoがモジュールを認識する config/initializers/torikago.rb Torikago.configure do |config| config.register( :foo, root: Rails.root.join("modules/foo"),
) config.register( :bar, root: Rails.root.join("modules/bar"), ) end
モジュールごとにBoxを作る
異なるモジュールは直接参照できない class Foo::DashboardsController def index @products = Bar::ProductsQuery.new(page: 1).call #
=> NameError: uninitialized constant Bar::ProductsQuery end end
異なるモジュールは直接参照できない
Rootモジュールは参照できる RootモジュールのQueryクラスを呼び出す例 class Foo::DashboardsController def index @customers = ::CustomersQuery.new(page: 1).call
# => #<ActiveRecord::Relation [#<Customer ...>, ...]> end end RootモジュールのModelを継承する例 class Foo::Customer < ::Customer scope :filter_by_foo_logic, -> { … } end
Rootモジュールは参照できる
Torikago::Gateway main boxを介してモジュールから他のモジュールへの参照を実現する class Foo::DashboardsController @products = Torikago::Gateway .build("Bar::ProductsQuery", page:
1) .invoke(:call) # => #<ActiveRecord::Relation [#<Bar::Product ...>, ...]> end
Torikago::Gateway
モジュール外からの参照ルールを定める Torikago::Gatewayによって、モジュールから他モジュールへの参照がで きるようになった これではPackage API以外も参照できてしまう
モジュール外からの参照ルールを定める Packwerkのように、YAMLで参照ルールを定義する 例: modules/bar/package_api.yml exports: Bar::ProductsQuery: methods: - call allowed_callers:
- foo
モジュール外からの参照ルールを定める Gatewayはモジュールの参照ルールによって呼び出しを許可する 許可されていない呼び出しが発生したら、例外を投げる class Foo::DashboardsController @products = Torikago::Gateway.build("Bar::ProductsQuery", page: 1)
.invoke(:call) # => #<ActiveRecord::Relation [#<Bar::Product ...>, ...]> end class Baz::DashboardsController @products = Torikago::Gateway.build("Bar::ProductsQuery", page: 1) .invoke(:call) # => Torikago::DependencyError: module dependency not allowed end
torikagoでできること
demo1: 特定のモジュールにのみモンキーパッチを適用する
demo1: 特定のモジュールにのみモンキーパッチを適用する foo boxでのみString#+ にモンキーパッチを当ててみる modules/foo/lib/monkey_patches/crying_string.rb ༎ຶ ༎ຶ class String
def +(other) decorated = dup decorated.concat(" ༼;´ ! !༽ ") decorated.concat(other.to_s) decorated end end
demo1: 特定のモジュールにのみモンキーパッチを適用する モジュールのconfigから、モンキーパッチを読み込む modules/foo/config/box_setup.rb require_relative "../lib/monkey_patches/crying_string" config/initializers/torikago.rb Torikago.configure do |config|
config.register( :foo, root: Rails.root.join("modules/foo"), setup: "config/box_setup.rb", ) config.register( :bar, root: Rails.root.join("modules/bar"), ) end
demo1: 特定のモジュールにのみモンキーパッチを適用する torikago + boxの結果
demo2: モジュール間でバージョンが異なるgemを管理する
実行空間が異なるのでgemを分離できる モジュールごとにgemのバージョンを管理できる 基本はrootのGemfileでバージョンを管理する モジュールごとにGemfileを定義して管理できる 特定のモジュールでだけ使いたいgem 特定のモジュールでだけ古いバージョンを使いたいgem
モジュールに限定したgem あくまで、rootのgemを参照する モジュールのGemfileで定義されているgemはそのモジュールからの み参照できる モジュールのGemfileとrootのGemfileで同じgemの異なるバージョン が定義されている場合は、モジュールのGemfileで定義されたバー ジョンが適用される
demo2: モジュール間でバージョンが異なるgemを管理する 異なるgemのバージョンを持つモジュールのPackage APIをmain boxから呼び出してみる 例: 郵便番号から住所を割り出すgem (jpostcode gem) root
(main box) jpostcode v1.0.0.20250901 foo モジュール (foo box) 定義なし barモジュール (bar box) jpostcode v1.0.0.20260507
None
torikagoでできないこと
Boxで分けられるもの・分けられないもの Boxごとに分けられる クラス / モジュール定義 モンキーパッチ ライブラリのロード状態 プロセス内で共有される Railsのランタイム 環境変数
Railsのautoloadやhelper探索
プロセス共通の状態を使うgemの分離 Rails / Active Support / Active Record Railsのクラスやモジュールと、それらが管理する設定・DB接続など を共有する
Bundler RUBYOPTやBUNDLER_SETUPなど、プロセス共通の環境へ作用す る
Ruby::Boxが与えるパフォーマンスへの影響 Ruby::Boxが有効の場合と無効の場合について、パフォーマンスへの影 響を確認する Ruby::Boxが無効の場合でも、Gatewayを介したモジュールの整理は できる ただし、直接参照ができてしまう
比較方法 同じRailsサンプルアプリケーションを実行 RUBY_BOX=0とRUBY_BOX=1を比較 RUBY_BOX=0の時はRuby::Boxによる隔離はできない 直接参照ができる 観測するアプリケーションのエンドポイント Foo/Bar 2つのモジュールのPackage APIを呼び出すRootのAPI FooモジュールのPackage
APIを呼び出すBarモジュールのAPI
パフォーマンスの計測結果 観測対象 Ruby::Box無効 Ruby::Box有効 差 Rails boot 456ms 2,830ms 6.2倍
Root API 初回リクエスト 16ms 449ms +433ms Root API 2回目以降リクエスト 2.6ms 4.3ms +2ms Root API 2回目以降 スループット 763.6 rps 688.2 rps 9.9%低下 モジュールのAPI初回リクエスト 14ms 445ms +431ms モジュールのAPI2回目以降リクエスト 2.2ms 4.3ms +2ms モジュールのAPI 2回目以降 スループット 770.3 rps 691.8 rps 10.2%低下
なぜこんなに遅くなったのか
初回リクエストが遅くなった理由 torikagoがBoxを作るのは必要になった時点でモジュール用Boxを作るよ うにしているため 今回のAPIだと、初回リクエストで2つのBoxを準備する必要がある
Rails起動が遅くなった理由 torikagoがBoxを作る前に遅延している torikagoなしの最小Railsでも、511msから3,159ms(約6.2倍)へ増加 torikagoの処理ではなく、Ruby::Box有効時のRails起動経路に原因が ある
スループットが低下した理由 作成済みのBoxは使い回すため、2回目以降に起動コストはかからない でも、Gatewayでの検証やBoxへの処理の受け渡しは毎回発生する 呼び出しが増えるほど負荷が積み重なり、毎秒処理数は約10%低下した
今後やりたいこと 起動時にすべてのモジュールをBoxに組み込み、cold requestをなくす Rails bootが遅くなっている原因を特定し、改善する 大規模なRailsアプリケーションへ導入し、実用できるようにする
まとめ torikagoはRuby::Boxを使ってモジュラモノリスの実行境界を分ける 実行境界が別れると、gemのバージョンをモジュールごとに参照できた り、モンキーパッチをモジュール内に閉じられたりする。 Ruby::Boxはまだ実験的な機能で、Railsの状態や環境変数など分離でき ないものがある上、性能面にも課題があるため、まだ実用段階ではない