Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
torikago - Ruby::Boxで照らすモジュラモノリスの実行境界
Search
nori
July 19, 2026
Programming
490
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
WebAssembly in Android Apps 〜 WASMはJNIの夢を見るか
keiji
1
110
From 6 People Classroom Meetup to 100 People Regional Conference / FOSS4G Hiroshima 2026
furukawayasuto
0
210
iOSDC Japan 2026 - Swiftで作って学ぼう!データベース自作入門
kaseken
2
170
Omarchy Tokyo やると聞いて UMPC 買ってセットアップしてきた
mtsmfm
0
140
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
490
MVNOの申込からeSIM開通までをiOSアプリでつなぐ- 本人確認・MNP・通信事業者基盤をまたぐ実装
satotakeshi
0
420
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
140
Vue Fes Japan 2026 タイムテーブル徹底解説
448jp
1
220
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
3
350
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
110
変化を抱擁するドキュメントの作り方 - ビジネスルール駆動開発がもたらす、コードとの新しい関係
ioki
2
210
XP祭りでしか伝わらないフリップネタ #xpjug
murabayashi
0
150
Featured
See All Featured
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
690
Abbi's Birthday
coloredviolet
4
10k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
580
Agile that works and the tools we love
rasmusluckow
331
22k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Chasing Engaging Ingredients in Design
codingconduct
0
310
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
510
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
42k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
First, design no harm
axbom
PRO
2
1.3k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
840
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の状態や環境変数など分離でき ないものがある上、性能面にも課題があるため、まだ実用段階ではない