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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
nori
July 19, 2026
Programming
350
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
JAWS-UG横浜 #102 AWSサ終供養LT会 成仏できない AWS サービスたち 〜本日、三体供養します〜
maroon1st
0
340
20260722_microCMSで考える、AI時代のコンテンツ運用設計
yosh1
0
380
改善しないと、タスクが回らない。 “てんこ盛りポジション” を引き継いだ情シスの、入社3ヶ月の業務改善録
krm963
0
260
5分で問診!Composer セキュリティ健康診断
codmoninc
0
930
Jindong: Introducing Declarative Haptics in Compose Multiplatform
l2hyunwoo
0
100
PHP初心者セッション2026 〜生成AIでは見えない裏側を知る:今だからLAMPを通して仕組みを学ぶ〜
kashioka
0
880
AWS DevOps AgentのAzure接続機能を検証して見えた活用法/Use Cases Verified for the AWS DevOps Agent's Azure Connectivity Feature
masakiokuda
1
220
プロポーザルを書いてもらう
pvcresin
0
300
GDG Korea Android: 2026 I/O Extended ~ What's new in Android development tools
pluu
0
220
PHP に部分適用が来るぞ!……ところで何それ?おいしいの? #phpcon / phpcon-2026
shogogg
0
610
ルールを書いて終わらせないハーネスエンジニアリング
yug1224
4
1.9k
Laravelで学ぶ Webアプリケーションチューニング入門/web_application_tuning_101
hanhan1978
4
1.7k
Featured
See All Featured
Mobile First: as difficult as doing things right
swwweet
225
10k
We Have a Design System, Now What?
morganepeng
55
8.3k
Being A Developer After 40
akosma
91
590k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
360
HDC tutorial
michielstock
2
790
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
890
The browser strikes back
jonoalderson
0
1.5k
Thoughts on Productivity
jonyablonski
76
5.3k
WENDY [Excerpt]
tessaabrams
11
39k
How to Think Like a Performance Engineer
csswizardry
28
2.7k
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の状態や環境変数など分離でき ないものがある上、性能面にも課題があるため、まだ実用段階ではない