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
65
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
The Bowling Game- From Imperative to Functional Programming - Part 1
philipschwarz
PRO
0
330
PHP初心者セッション2026 〜生成AIでは見えない裏側を知る:今だからLAMPを通して仕組みを学ぶ〜
kashioka
0
460
共通化で考えるべきは、実装より公開する型だった
codeegg
0
240
なぜ関数型プログラミングで「型」と「証明」が語られるのか #fp_matsuri
kajitack
3
940
symfony/aiとlaravel/boost
77web
0
130
どこまでゆるくて許されるのか
tk3fftk
0
490
Claude Opus 4.6以後の受託開発エンジニアの変化(Claude Code開発ノウハウ大公開スペシャルbyクラスメソッド)
iidatakuma
1
770
作るコストが小さくなった時代 幸せに働くために改めて考えたいこと 〜エンジニアとして価値を出し続けるために注視している二分野〜
yuppeeng
0
100
才能?センス?知らん、 続けたもん勝ちだ。-- 結婚・出産・癌を越えてなお、私がプロダクトを創り続ける理由
16bitidol
2
860
ランチタイムLT会3周年!ランチタイムLT会を3年間続けられたお話
y0hgi
1
140
えっ!!コードを読まずに開発を!?
hananouchi
0
210
AIエージェントで 変わるAndroid開発環境
takahirom
2
660
Featured
See All Featured
How to build a perfect <img>
jonoalderson
1
5.8k
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
870
The Invisible Side of Design
smashingmag
301
52k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
sira's awesome portfolio website redesign presentation
elsirapls
0
300
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Leo the Paperboy
mayatellez
8
1.9k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
220
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
460
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
1
3.7k
Become a Pro
speakerdeck
PRO
31
6k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
720
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の状態や環境変数など分離でき ないものがある上、性能面にも課題があるため、まだ実用段階ではない