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
66
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
「正の参照」と 「負の導出」で組む ハーネスエンジニアリング
cottpan
1
140
【やさしく解説 設計編・中級 #4】ルールの寿命と、システムの年輪
panda728
PRO
2
140
関数型プログラミングのメリットって何だろう?
wanko_it
0
180
Claude Team Plan導入・ガイド
tk3fftk
0
210
エンジニア向け会社紹介/Findy Company Profile
findyinc
6
360k
Welcome to the "Parametricity" 🏙️ − Generic だけど Specific な世界 −
guvalif
PRO
1
170
Foundation Models frameworkで画像分析
ryodeveloper
1
120
1B+ /day規模のログを管理する技術
broadleaf
0
140
これからAgentCoreを触る方へトレンドはGatewayです
har1101
6
500
The Bowling Game- From Imperative to Functional Programming - Part 1
philipschwarz
PRO
0
330
SLOをサービス品質の共通言語にするために 取り組んできたこと
wakana0222
0
500
LaravelLive Japan の裏方のすべて — 第188回 PHP勉強会@東京 (2026-06-24)
suguruooki
2
150
Featured
See All Featured
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.6k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
630
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
Testing 201, or: Great Expectations
jmmastey
46
8.2k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Side Projects
sachag
455
43k
First, design no harm
axbom
PRO
2
1.2k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
320
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
260
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
450
Fashionably flexible responsive web design (full day workshop)
malarkey
408
67k
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の状態や環境変数など分離でき ないものがある上、性能面にも課題があるため、まだ実用段階ではない