torikago 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4067561b7267c62468f0ba974be7a68e536fa620cc25c7356ba643f774bc14d9
4
- data.tar.gz: 5ceb5e3693ba1a767a2e93f439384df0d568306beabbbb62efb9538dc807dc79
3
+ metadata.gz: 0af7286987b986ed0366120312c488e768ab119e79cf025ea20b8e1e51039878
4
+ data.tar.gz: 0235edbb0503573d4caaaf702284f75604989d99ccee1cf189cd81a1eb04c178
5
5
  SHA512:
6
- metadata.gz: 9bbe65e72b673873e80dfc51c64de86a1c3240976ebad98f964d47abdef046b99058dc8f33365d9aba9714e6e2a92685db98737084e7b81bb34cc17321fe37fa
7
- data.tar.gz: 6968dad15fcdc9e710819468925d4e5da7778b90d53615d31308fcba9f58a9f99d7a6d6f9082d6091618c480cef96083a7476ea013c07c28d3418e55f0f4d9ef
6
+ metadata.gz: 06628e161d1f2210bcca6e99f1dd7bb9d5697ed89f863ae351d349ec3844030185a61856e195a4c41cff3605b6d58f818986d21fb14ca8b52948ae68b58c8783
7
+ data.tar.gz: 35cf7e1d20061c198e7fcf2fcaadd4f9014dc58ed9d08fc4f5307f5ae8ca494ba79599519eaa4747763cef6219594e32c92b128d09e01be9cd4cb188b5dfdf19
data/README.ja.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `torikago`は、Railsのmodular monolithでmoduleごとの実行境界を扱うためのgemです。`packwerk`や`Rails::Engine`で構造上の境界を作るだけでなく、`Ruby::Box`を使って実行時の境界も強くすることを目指しています。
4
4
 
5
- `torikago`では、module間の呼び出しを`Torikago::Gateway.call(...)`に集約し、各modulePackage APIと呼び出し可能なmoduleを事前に定義します。これによって、意図していないmodule間参照を実行時に防ぎやすくします。
5
+ `torikago`では、module間の呼び出しを`Torikago::Gateway.invoke(...)`に集約し、各moduleが公開するPackage APIのclass・methodと呼び出し可能なmoduleを事前に定義します。これによって、意図していないmodule間参照を実行時に防ぎやすくします。
6
6
 
7
7
  ![torikago architecture](docs/image.png)
8
8
 
@@ -16,6 +16,7 @@ Torikago.configure do |config|
16
16
  :foo,
17
17
  root: Rails.root.join("modules/foo"),
18
18
  entrypoint: "app/package_api", # optional
19
+ rails_engine: true, # optional
19
20
  setup: "config/box_setup.rb", # optional
20
21
  gemfile: "Gemfile" # optional
21
22
  )
@@ -29,6 +30,10 @@ end
29
30
  - `entrypoint`
30
31
  - public APIを探索するディレクトリ、またはその配下のファイル
31
32
  - 未指定時は`app/package_api`
33
+ - `rails_engine`
34
+ - moduleが所有するRails::Engineのroute setを有効にし、Rails runtimeを同じBoxへ読み込む
35
+ - host routerでは`Foo::Engine`ではなく`Torikago::RackEndpoint.new(:foo)`をmountする
36
+ - host routeから`Torikago.action(...)`でControllerを呼ぶ場合は不要
32
37
  - `setup`
33
38
  - Box boot前に読み込むsetup hook
34
39
  - monkey patchやbox固有の初期化処理に使う
@@ -42,26 +47,101 @@ module側では、公開するPackage APIと、どのmoduleから呼べるかを
42
47
  ```yaml
43
48
  exports:
44
49
  Foo::ListProductsQuery:
50
+ methods:
51
+ - call
52
+ - execute!
45
53
  allowed_callers:
46
54
  - baz
47
55
  ```
48
56
 
49
57
  module自身からの呼び出しとmain boxからの呼び出しは許可されます。`allowed_callers`は、他moduleからの参照だけを制限します。
50
58
 
51
- 呼び出し側では、対象のclass名を使って呼び出します。
59
+ constructorに引数がない場合は、公開methodを直接指定します。
52
60
 
53
61
  ```ruby
54
- Torikago::Gateway.call("Foo::ListProductsQuery")
62
+ Torikago::Gateway.invoke("Foo::ListProductsQuery", :call)
55
63
 
56
- # 引数を渡す場合
57
- Torikago::Gateway.call("Bar::SubmitOrderCommand", title: "Book")
64
+ # method引数はmethod名より後ろへ渡す
65
+ Torikago::Gateway.invoke("Bar::SubmitOrderCommand", :execute!, title: "Book")
58
66
  ```
59
67
 
60
- `Torikago::Gateway.call(...)`は、class名から対象moduleを解決し、そのmoduleのexportされたPackage API定義を確認したうえで、対象Boxの中で`new.call(...)`を実行します。
68
+ constructorに引数がある場合は`build`を使います。`build`の引数は`new`だけへ、`invoke`の引数は指定したpublic methodだけへ渡ります。
69
+
70
+ ```ruby
71
+ Torikago::Gateway
72
+ .build("Foo::ListProductsQuery", page: 2)
73
+ .invoke(:execute!, per_page: 20)
74
+ ```
75
+
76
+ 対象Box内で`Foo::ListProductsQuery.new(page: 2).public_send(:execute!, per_page: 20)`を実行します。GatewayはBoxをbootする前にclass・method・callerを`package_api.yml`と照合します。private methodは呼べず、constructorや対象methodの例外は包まずそのまま伝播します。
77
+
78
+ `Gateway.call`は削除されました。`Gateway.call("Foo::Query", value)`は`Gateway.invoke("Foo::Query", :call, value)`へ変更し、manifestへ`methods: [call]`を追加してください。`update-package-api`は既存の`methods`を保持し、新規発見したentryには`methods: []`を生成するため、公開methodを明示的に選ぶ必要があります。
79
+
80
+ ## Root Moduleの定数参照
81
+
82
+ Registered Moduleから、Railsアプリケーション本体(Root Module)のtop-level定数はmanifestへの宣言なしで参照できます。main boxの同じclass/module objectを共有するため、QueryやCommandの呼び出しだけでなく、継承にも利用できます。
83
+
84
+ Module namespace内から参照するときは、`::`で始まる絶対定数参照を使います。これにより、`Foo::Order`のtypoがRootの`::Order`へ暗黙にfallbackすることを防ぎます。
85
+
86
+ ```ruby
87
+ # Railsアプリケーション本体
88
+ class Order
89
+ end
90
+
91
+ class CustomerQuery
92
+ def self.call(customer_id:)
93
+ # ...
94
+ end
95
+ end
96
+
97
+ # config.register(:foo, ...)されたmodule内
98
+ class Foo::SpecialOrder < ::Order
99
+ end
100
+
101
+ ::CustomerQuery.call(customer_id: 1)
102
+ ```
103
+
104
+ ownershipはtop-level定数単位で判定します。top-level定数の定義元が`config.register(..., root:)`配下なら、その定数は別のModule Boxへ自動公開されません。Root-owned class/moduleは同じオブジェクトを共有するため、そのnamespaceをregistered rootから再オープンして子定数を追加すると、子定数だけを隔離できません。torikagoは検出可能な場合にnamespace全体の共有を拒否しますが、mixed-ownership namespace自体をサポートしません。隔離が必要な定数は、module-ownedなtop-level namespace配下へ配置してください。
105
+
106
+ Module Box内に同名定数がある場合は、そのmodule-local定数が優先されます。Root ModuleからRegistered Module、およびRegistered Module間の呼び出しには、引き続き`Torikago::Gateway`を使用してください。
61
107
 
62
108
  ## Example app
63
109
 
64
- `example/rails-modular-monolith/`に、最小のRails example appが入っています。
110
+ `example/rails-modular-monolith-with-rails-engine/`に、Rails::EngineをBox内で動かすexample appが入っています。
111
+
112
+ ```ruby
113
+ # config/routes.rb(host application)
114
+ mount Torikago::RackEndpoint.new(:foo) => "/"
115
+ ```
116
+
117
+ endpointは現在のlazy bootを維持します。最初のHTTP requestまたはGateway呼び出しでmodule Boxを生成し、以降は同じBoxを再利用します。
118
+ この橋渡しにはRack互換のroute endpointを使います。process全体へRack middlewareを
119
+ 追加する仕組みではありません。
120
+
121
+ Controllerの隔離にRails::Engineは必須ではありません。host側のrouteから、
122
+ torikagoがmodule Box内だけで解決するControllerへdispatchできます。
123
+
124
+ ```ruby
125
+ # config/initializers/torikago.rb
126
+ Torikago.configure do |config|
127
+ config.register(:qux, root: Rails.root.join("modules/qux"))
128
+ end
129
+
130
+ # config/routes.rb(host application)
131
+ get "/qux/showcase" => Torikago.action(
132
+ :qux,
133
+ "Qux::ShowcaseController",
134
+ :show
135
+ )
136
+ ```
137
+
138
+ Controllerの所有moduleは、`config/initializers/torikago.rb`へ登録したmodule名と
139
+ rootで決まり、このhost route方式のために追加の`config.register` optionは必要ありません。
140
+ ただし、現在のRails integrationでは、実際のController classを登録module名から導かれる
141
+ namespace内に置く必要があります。namespaceを持たないRails Controllerへの対応は
142
+ [issue #15](https://github.com/se4weed/torikago/issues/15)で追跡しています。
143
+ Controller、Model、helper、view、Package APIをhost applicationのautoload pathへ
144
+ 追加する必要はありません。
65
145
 
66
146
  ## 使い方
67
147
 
@@ -74,15 +154,15 @@ bundle exec rake test
74
154
  ### example appのテスト
75
155
 
76
156
  ```sh
77
- cd example/rails-modular-monolith
78
- RUBY_BOX=1 bundle exec rails test
157
+ cd example/rails-modular-monolith-with-rails-engine
158
+ bundle exec bin/box-rails test
79
159
  ```
80
160
 
81
161
  ### example appの起動
82
162
 
83
163
  ```sh
84
- cd example/rails-modular-monolith
85
- RUBY_BOX=1 bundle exec rails s
164
+ cd example/rails-modular-monolith-with-rails-engine
165
+ bundle exec bin/box-rails s
86
166
  ```
87
167
 
88
168
  `Ruby::Box`を実際に有効にするには`RUBY_BOX=1`が必要です。
@@ -100,15 +180,17 @@ bundle exec ruby exe/torikago --help
100
180
  - `torikago init`
101
181
  - 対話式で`package_api.yml`と`config/initializers/torikago.rb`を生成する
102
182
  - `torikago check`
103
- - `Gateway.call`とmanifestの整合性を検証する
183
+ - `Gateway.invoke`および`Gateway.build(...).invoke(...)`とmanifestの整合性を検証する
104
184
  - `torikago update-package-api [BOX]`
105
185
  - 設定済みentrypointから`package_api.yml`を更新する
106
186
 
107
- `torikago check`は、`Torikago::Gateway.call("...")`の呼び出しを走査し、
187
+ `torikago check`は、`Ripper`で静的なGateway呼び出しを走査し、
108
188
 
109
189
  - manifestにそのclassが定義されているか
190
+ - 呼び出すmethodが空でない`methods`配列に定義されているか
110
191
  - 呼び出し元moduleが`allowed_callers`に含まれているか
111
192
  - manifest上のclassに対応するファイルが存在するか
193
+ - 静的に確認できる場合、公開したinstance methodが実装されているか
112
194
 
113
195
  を確認します。
114
196
 
@@ -130,14 +212,19 @@ bundle exec ruby exe/torikago --help
130
212
  - segfaultや不安定さに遭遇することがある
131
213
  - Railsや一部gemとの相性問題がある
132
214
  - とくにVM全体へ影響するglobal-effect gemは、きれいに分離しきれない
133
- - full `Rails::Engine` confinementを素直にやるのはまだ難しい
215
+ - Rails integrationにはprocess-globalなframework stateが残る
216
+ - Rails initializerやnative extensionはBoxごとに完全分離されない
217
+ - 実際のRails Controllerは、現在は登録moduleのnamespace内に置く必要がある
218
+ - namespaceを持たないControllerへの対応はissue #15で追跡中
134
219
 
135
220
  代表的な例外:
136
221
 
137
222
  - `Torikago::DependencyError`
138
223
  - 許可されていないmodule間参照
139
224
  - `Torikago::PublicApiError`
140
- - manifestに宣言されていないPackage APIの呼び出し
225
+ - manifestに宣言されていないPackage API classまたはmethodの呼び出し
226
+ - `Torikago::BoxUnavailableError`
227
+ - `RUBY_BOX=1`を指定したが対象Boxを生成・準備できなかった場合。main processへはfallbackしない
141
228
  - `Torikago::GemfileOverrideError`
142
229
  - Box用Gemfileの解決やactivateに失敗したとき
143
230
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `torikago` is a gem for introducing per-module runtime boundaries to Rails modular monoliths. It aims to strengthen runtime isolation with `Ruby::Box`, in addition to the structural boundaries you can already get from tools like `packwerk` and `Rails::Engine`.
4
4
 
5
- With `torikago`, module-to-module calls are funneled through `Torikago::Gateway.call(...)`, and each module declares which Package APIs it exposes and which modules may call them. This makes it easier to prevent unintended cross-module references at runtime.
5
+ With `torikago`, module-to-module calls are funneled through `Torikago::Gateway.invoke(...)`, and each module declares which Package API classes and methods it exposes and which modules may call them. This makes it easier to prevent unintended cross-module references at runtime.
6
6
 
7
7
  ![torikago architecture](docs/image.png)
8
8
 
@@ -16,6 +16,7 @@ Torikago.configure do |config|
16
16
  :foo,
17
17
  root: Rails.root.join("modules/foo"),
18
18
  entrypoint: "app/package_api", # optional
19
+ rails_engine: true, # optional
19
20
  setup: "config/box_setup.rb", # optional
20
21
  gemfile: "Gemfile" # optional
21
22
  )
@@ -29,6 +30,10 @@ The main `config.register` options are:
29
30
  - `entrypoint`
30
31
  - the directory, or file under that directory, used to discover public APIs
31
32
  - defaults to `app/package_api`
33
+ - `rails_engine`
34
+ - enables the module-owned Rails::Engine route set and loads its Rails runtime into the same Box
35
+ - mount `Torikago::RackEndpoint.new(:foo)` in the host router instead of mounting `Foo::Engine`
36
+ - not required when host routes dispatch controllers with `Torikago.action(...)`
32
37
  - `setup`
33
38
  - a setup hook loaded before Box boot completes
34
39
  - useful for monkey patches or Box-specific initialization
@@ -42,26 +47,105 @@ On the module side, declare the Package APIs you expose and which modules may ca
42
47
  ```yaml
43
48
  exports:
44
49
  Foo::ListProductsQuery:
50
+ methods:
51
+ - call
52
+ - execute!
45
53
  allowed_callers:
46
54
  - baz
47
55
  ```
48
56
 
49
57
  Calls from the module itself and from the main box are allowed implicitly. `allowed_callers` only restricts calls coming from other modules.
50
58
 
51
- Call a Package API by class name:
59
+ For an argumentless constructor, invoke the exported public method directly:
52
60
 
53
61
  ```ruby
54
- Torikago::Gateway.call("Foo::ListProductsQuery")
62
+ Torikago::Gateway.invoke("Foo::ListProductsQuery", :call)
55
63
 
56
- # with arguments
57
- Torikago::Gateway.call("Bar::SubmitOrderCommand", title: "Book")
64
+ # arguments after the method name go to that method
65
+ Torikago::Gateway.invoke("Bar::SubmitOrderCommand", :execute!, title: "Book")
58
66
  ```
59
67
 
60
- `Torikago::Gateway.call(...)` resolves the target module from the class name, checks the exported Package API declaration for that module, and then runs `new.call(...)` inside the target Box.
68
+ Use `build` when the constructor takes arguments. `build` arguments go only to `new`; `invoke` arguments go only to the selected public method:
69
+
70
+ ```ruby
71
+ Torikago::Gateway
72
+ .build("Foo::ListProductsQuery", page: 2)
73
+ .invoke(:execute!, per_page: 20)
74
+ ```
75
+
76
+ This runs `Foo::ListProductsQuery.new(page: 2).public_send(:execute!, per_page: 20)` entirely inside the target Box. Before booting that Box, Gateway checks the class, method, and caller against `package_api.yml`. Private methods cannot be invoked and target constructor/method exceptions are propagated unchanged.
77
+
78
+ `Gateway.call` has been removed. Migrate `Gateway.call("Foo::Query", value)` to `Gateway.invoke("Foo::Query", :call, value)`, and add `methods: [call]` to its manifest entry. `update-package-api` preserves existing `methods`; newly discovered entries use `methods: []` so the public surface must be chosen explicitly.
79
+
80
+ ## Referencing Root Module constants
81
+
82
+ A Registered Module can reference top-level constants from the Rails application (the Root Module) without declaring them in a manifest. Torikago shares the same class or module object from the main Box, so Root constants can be used for inheritance as well as Query or Command calls.
83
+
84
+ Use an absolute constant reference beginning with `::` from inside a module namespace. This prevents a typo such as `Foo::Order` from silently falling back to the Root `::Order`.
85
+
86
+ ```ruby
87
+ # Rails application
88
+ class Order
89
+ end
90
+
91
+ class CustomerQuery
92
+ def self.call(customer_id:)
93
+ # ...
94
+ end
95
+ end
96
+
97
+ # Inside a config.register(:foo, ...) module
98
+ class Foo::SpecialOrder < ::Order
99
+ end
100
+
101
+ ::CustomerQuery.call(customer_id: 1)
102
+ ```
103
+
104
+ Ownership is atomic at the top-level constant. A top-level constant whose definition is below a `config.register(..., root:)` path is not exposed automatically to another Module Box. Because a Root-owned class or module is shared as the same object, reopening that namespace from a registered root cannot isolate only the newly added child constants. Torikago rejects the whole namespace when it can detect this conflict, but mixed-ownership namespaces are not supported. Put constants that need isolation below a module-owned top-level namespace instead.
105
+
106
+ A module-local constant with the same name takes precedence. Calls from the Root Module to a Registered Module, and calls between Registered Modules, must still use `Torikago::Gateway`.
61
107
 
62
108
  ## Example app
63
109
 
64
- A minimal Rails example app lives in `example/rails-modular-monolith/`.
110
+ The Rails::Engine confinement example lives in
111
+ `example/rails-modular-monolith-with-rails-engine/`.
112
+
113
+ ```ruby
114
+ # config/routes.rb (host application)
115
+ mount Torikago::RackEndpoint.new(:foo) => "/"
116
+ ```
117
+
118
+ The endpoint preserves lazy boot: the module Box is created on the first HTTP
119
+ request or Gateway invocation and reused afterward.
120
+ Torikago uses Rack-compatible route endpoints for this bridge; it does not add
121
+ process-wide Rack middleware.
122
+
123
+ Rails::Engine is optional for controller isolation. A host-owned route can
124
+ dispatch to a controller constant that Torikago resolves only inside the module
125
+ Box:
126
+
127
+ ```ruby
128
+ # config/initializers/torikago.rb
129
+ Torikago.configure do |config|
130
+ config.register(:qux, root: Rails.root.join("modules/qux"))
131
+ end
132
+
133
+ # config/routes.rb (host application)
134
+ get "/qux/showcase" => Torikago.action(
135
+ :qux,
136
+ "Qux::ShowcaseController",
137
+ :show
138
+ )
139
+ ```
140
+
141
+ The module name and root registered in `config/initializers/torikago.rb` identify
142
+ the owner, and no additional `config.register` option is required for this
143
+ host-route mode. The current Rails integration still requires real controller
144
+ classes to live under the namespace derived from the registered module name.
145
+ Support for non-namespaced Rails controllers is tracked in
146
+ [issue #15](https://github.com/se4weed/torikago/issues/15). Controllers, models,
147
+ helpers, views, and Package APIs stay under the registered module root; they do
148
+ not need to be added to the host application's autoload paths.
65
149
 
66
150
  ## Usage
67
151
 
@@ -74,15 +158,15 @@ bundle exec rake test
74
158
  ### Run example app tests
75
159
 
76
160
  ```sh
77
- cd example/rails-modular-monolith
78
- RUBY_BOX=1 bundle exec rails test
161
+ cd example/rails-modular-monolith-with-rails-engine
162
+ bundle exec bin/box-rails test
79
163
  ```
80
164
 
81
165
  ### Start the example app
82
166
 
83
167
  ```sh
84
- cd example/rails-modular-monolith
85
- RUBY_BOX=1 bundle exec rails s
168
+ cd example/rails-modular-monolith-with-rails-engine
169
+ bundle exec bin/box-rails s
86
170
  ```
87
171
 
88
172
  `RUBY_BOX=1` is required to actually enable `Ruby::Box`.
@@ -100,15 +184,17 @@ Main commands:
100
184
  - `torikago init`
101
185
  - interactively generate `package_api.yml` files and `config/initializers/torikago.rb`
102
186
  - `torikago check`
103
- - validate `Gateway.call` usage against manifests
187
+ - validate `Gateway.invoke` and `Gateway.build(...).invoke(...)` usage against manifests
104
188
  - `torikago update-package-api [BOX]`
105
189
  - regenerate `package_api.yml` from the configured entrypoint
106
190
 
107
- `torikago check` scans `Torikago::Gateway.call("...")` usage and verifies:
191
+ `torikago check` uses `Ripper` to scan static Gateway invocations and verifies:
108
192
 
109
193
  - the class is declared in a manifest
194
+ - the invoked method is listed in the non-empty `methods` array
110
195
  - the caller module is included in `allowed_callers`
111
196
  - the manifest entry has a matching implementation file
197
+ - the exported public instance method is defined when it can be checked statically
112
198
 
113
199
  ## About `RUBY_BOX=1` and boot
114
200
 
@@ -128,14 +214,19 @@ These are pragmatic workarounds for the current example app, not a finalized lon
128
214
  - segfaults and instability can happen
129
215
  - Some gems do not cooperate well with this model
130
216
  - especially global-effect gems that influence the whole VM
131
- - Full `Rails::Engine` confinement is still difficult to do cleanly
217
+ - Rails integration still relies on process-global framework state
218
+ - Rails initializers and native extensions are not completely isolated per Box
219
+ - Real Rails controllers currently need the registered module namespace
220
+ - non-namespaced controller support is tracked in issue #15
132
221
 
133
222
  Common errors:
134
223
 
135
224
  - `Torikago::DependencyError`
136
225
  - an unauthorized cross-module reference
137
226
  - `Torikago::PublicApiError`
138
- - calling a Package API that is not declared in the manifest
227
+ - calling a Package API class or method that is not declared in the manifest
228
+ - `Torikago::BoxUnavailableError`
229
+ - `RUBY_BOX=1` was requested but the target Box could not be created or prepared; Torikago does not fall back to the main process
139
230
  - `Torikago::GemfileOverrideError`
140
231
  - failure while resolving or activating a Box-specific Gemfile override
141
232