ADR-12 — dry-rbプラグインパッケージング
ステータス: Accepted, 2026-05-16.
Rigorのdry-rbアダプタプラグインのパッケージング形態を決定し、個々のrigor-dry-*の作業を基本事項を再議論することなく開始できるようにします。
コンテキスト
Section titled “コンテキスト”dry-rb gemファミリーは相補的なgemのツリーです: dry-types、dry-struct、dry-validation、dry-monads、dry-schema、dry-effects、dry-events、dry-system、dry-files、その他いくつか。これらはイディオム(コンストラクタスタイルのクラス、structベースの属性リスト、Monadライクな戻り値エンベロープ)を共有しますが、各gemは独自のDSL面を公開します。サーベイ20260509-dry-plugins-roadmap.mdは、静的解析で重要となるgem、それらのgem間の依存関係、各gemが公開する型形成面の拘束的なインベントリです。
rigor-dry-structはv0.1.5で最初のdry-*プラグインとして出荷され、ADR-16のTier C(heredocテンプレート)基板を実践しました。rigor-dry-types、rigor-dry-validation、rigor-dry-monadsの形態はrigor-dry-structと十分に似ているため、次のプラグインが出荷される前にパッケージングの問い —1つのメガgemか? gemごとか? 中粒度バンドルか? メタアンブレラか?— に明示的に答える必要があります。
同じ問いはRailsプラグインファミリーについてdocs/design/20260508-rails-plugins-roadmap.mdで答えられました: plugins/rigor-<id>/の下にステージングされたgemごとのプラグイン、各プラグインの契約(contract)が安定したらgit subtree splitで抽出、将来のrigor-railsメタgemがTier 1+2プラグインをgem依存として列挙する。同じパターンがdry-rbにも有効です。
Railsプラグインファミリーパターンに合致する、gemごとのプラグイン + メタアンブレラ。
- 各dry-* gemは独自のRigorプラグインを持つ:
rigor-dry-types、rigor-dry-struct、rigor-dry-validation、rigor-dry-monads、rigor-dry-schema、… 上流のgem境界と1対1で対応する。 - プラグインは
rigor-plugin-authorSKILLの規律に従い、plugins/rigor-dry-<id>/の下にステージングされる。 - プラグインの契約が安定したら、Railsプラグインファミリーが使用するのと同じスケジュールとレディネスチェックリストで、
git subtree splitにより独自の公開gemとして抽出される。 - 将来の
rigor-dry-rbメタgemがツリー内プラグインをgem依存として宣言し、Gemfile 1行でユーザーがスタック全体をオプトインできるようにする。
却下された代替案は「考慮した代替案」セクションに記録されています。
サーベイ20260509-dry-plugins-roadmap.mdからのボトムアップ依存順序が引き継がれます。
rigor-dry-types(Tier A基盤)。Types::String/Types::Coercible::Integer/Types::Strict::Bool/ …という定数を認識し、属性ごとの型としてNominal[String]等を貢献する。すべての上位tierプラグインの基盤。rigor-dry-struct(Tier A、v0.1.5でLANDED)。ADR-16のTier C基板経由ですでに出荷済み。ADR-12以前のパッケージングはすでにこの決定に整合していたため、再パッケージング不要。rigor-dry-validation(Tier A)。Dry::Validation::Contractサブクラスとそのschema/paramsDSLを認識する。キーごとの型についてはrigor-dry-typesの上に構築。rigor-dry-monads(Tier B)。戻り型をResult[T, E]/Maybe[T]エンベロープでラップする。Tier Aプラグインからは独立しているが、Tier Aプラグインが内側のTを型付けする場合は共存する。rigor-dry-schema(Tier A)。dry-validationに類似するがスタンドアロン。実務ではvalidationよりも優先度が低い。rigor-dry-effects(Tier B)。エフェクトシステムDSL。十分にニッチなため需要駆動。- Tier C / D / E / F — サーベイの分類に従って先送り。
次のスライス(slice)はrigor-dry-types(Tier A基盤)です。
プラグイン契約の再利用
Section titled “プラグイン契約の再利用”ADR-16の4つの基板Tierが構成要素となります。
| dry-* gem | 基板Tier(想定) | 注記 |
|---|---|---|
dry-types | 手書きウォーカー(定数解決) | 各Types::Fooリテラルは定数参照;基板Tierに乗せるべきクラス本体DSLが存在しない。 |
dry-struct | Tier C(heredocテンプレート) | LANDED。attribute :name, Tごとのメソッド発行。 |
dry-validation | Tier A(block-as-method) + ウォーカー | schema { … }ブロックがスキーマDSLレシーバーに対して実行される;ブロック面についてはblock-as-methodを、キーについては手書きウォーカーを組み合わせる。 |
dry-monads | flow_contribution_for | 戻り型書き換え(def x; Success(42); end→Result[Integer, untyped])は完全に戻り型計算;クラス本体DSLは存在しない。 |
dry-schema | dry-validationと同じ | 対称なDSL。 |
dry-effects | Tier AまたはTier B | 観察されたイディオム的使用に依存 — 具体的なプラグインが始まるまで先送り。 |
プラグイン作者は上流DSLの形状に応じて基板Tierを選ぶ;ここでのパッケージング決定は、各プラグインがどのTierを使うことになるかとは直交する。
プラグイン横断のファクト依存
Section titled “プラグイン横断のファクト依存”上位tierプラグインはADR-9のPlugin::FactStoreチャネル経由でTier-Aプラグインのファクト(fact)を消費します。dry-*プラグインの正準なチャネル名は次のとおり。
:dry_type_aliases—rigor-dry-typesが発行、rigor-dry-struct/rigor-dry-validation/rigor-dry-schemaが消費し、MyTypes::Email = Types::String.constrained(format: …)エイリアスがプラグイン横断で可視となる。:dry_struct_attributes—rigor-dry-structが発行、各structの属性リストを知る必要がある下流プラグイン(例: シリアライザプラグイン)が消費。:dry_validation_keys—rigor-dry-validationが発行、コントローラーがparams検証をdry-validation Contractに委譲した場合のrigor-actionpackstrong-params認識器が消費。
正確なfact-storeペイロード形状はプラグインごとに決定する;このADRはプラグイン横断の協調パターンにのみコミットする。
公開API漂流面
Section titled “公開API漂流面”ADR-12自体は新しいコード面を追加しません。プラグインごとのgemspecがそれぞれ公開APIを成長させ、spec/rigor/public_api_drift_spec.rbがこれをピン留めしなければなりません;Railsプラグインファミリーの先例に従い、プラグイン内部のクラス(ウォーカー、fact-storeペイロードクラス)は漂流スナップショットの外側に残る — Plugin::Baseサブクラスとその#manifest形状のみがピン留めされる。
ワーキング決定
Section titled “ワーキング決定”WD1 — なぜgemごとなのか、メガrigor-dry-rb gemではないのか?
Section titled “WD1 — なぜgemごとなのか、メガrigor-dry-rb gemではないのか?”3つの議論をまとめて。
- 肥大化。dry-typesのみのコードを解析するユーザーは、必要としないvalidation / monads / schemaウォーカーを読み込むことになる。ADR-2に従ったウォーカーtier順序(RBS >
RBS::Extended> プラグイン > …)は、レシーバークラスが一度も現れなくても読み込まれたすべてのプラグインがディスパッチに参加することを意味するため、バンドルサイズが重要。 - 結合。dry-* gemは上流で独立してバージョン管理される(
dry-types1.7とdry-monads1.6など)。メガgemは、1つのプラグインのウォーカーしか変更されていない場合でも、依存のいずれかがバンプされるたびにリリースする必要がある。 - 先例。Railsプラグインファミリーはすでにgemごと + メタアンブレラを選択しており、メタgem(計画中の
rigor-rails)がTier 1+2プラグインをgem依存として列挙する。dry-rbでも同じパターンを繰り返すことで、エコシステムが一貫したものとなる。
WD2 — なぜ中粒度バンドル(例: types + struct + validation + schemaのrigor-dry-data)にしないのか?
Section titled “WD2 — なぜ中粒度バンドル(例: types + struct + validation + schemaのrigor-dry-data)にしないのか?”中粒度バンドルは魅力的に見える。なぜならdry-rbサーベイはファミリーをTier A〜Fに分類するからだ。しかし、その分類は解析的形状(プラグインが何をするか)によるものであって、ユーザーが何をインストールするかによるものではない。ユーザーはdry-validation(Tier A)なしでdry-struct(Tier A)を使うかもしれない — クラスタは共インストールを予測しない。gemごとの方が実際のGemfileパターンに忠実である。
例外は、2つの上流gemが密結合していてプラグインウォーカーを分割することが不格好になる場合(例: dry-schema + dry-validationはキー型強制DSLを共有する)。プラグイン作者はウォーカーコードが真に重複する場合に2つのプラグインを1つにマージしてもよい(MAY);それ以外のすべての場合にはパッケージング決定はgemごとのまま。
WD3 — Subtree-splitレディネスチェックリスト(Railsから継承)
Section titled “WD3 — Subtree-splitレディネスチェックリスト(Railsから継承)”rigor-dry-<id>プラグインがgit subtree splitの準備が整うのは次のとき。
- プラグインの
manifest、ウォーカー、統合specが、次の1ヶ月の変更が追加的(破壊的なシグネチャ変更なし)と言える程度に安定している。 spec/integration/plugins/<plugin_name>_plugin_spec.rbの下に、プラグインの外部クローンに対してCIされる程度に作り込まれた統合specがある。public_api_drift_spec.rbがプラグインのPlugin::Baseサブクラスとmanifest形状をピン留めしている。- プラグインの
README.mdに「このプラグインがすること / しないこと」セクションがあり、ユーザーがそれを必要とするかどうか判断できる。
チェックリストはRailsプラグインのレディネス条件と一致する;dry固有の例外はなし。
WD4 — メタアンブレラrigor-dry-rbは先送り
Section titled “WD4 — メタアンブレラrigor-dry-rbは先送り”アンブレラgemは計画されているがコミットはされていない。着地するのは次のとき。
- 3つ以上の
rigor-dry-*プラグインがsubtree split経由で出荷されている;AND - ユーザーが「スタック全体の1行インストール」を、メタgemの保守オーバーヘッド(リリース調整、サブgem間の依存バージョンピン)を正当化する形で要求している。
それまでは、ユーザーは個々のgemをGemfileに列挙できる。
WD5 — rigor-dry-typesが次の具体的なスライス
Section titled “WD5 — rigor-dry-typesが次の具体的なスライス”次の実装ステップはplugins/rigor-dry-types/です。すべての上位tier dry-*プラグインが読むべき基盤。プラグインの作業は、Types::String / Types::Coercible::Integer / Types::Strict::Boolの定数参照を認識し、属性ごとの型を貢献する手書きウォーカーに集中する。これにより下流プラグイン(rigor-dry-struct、rigor-dry-validation)がADR-9の:dry_type_aliasesチャネル経由でそれらを拾える。
考慮した代替案
Section titled “考慮した代替案”- 単一の
rigor-dry-rbメガgem。WD1に従って却下。 - tier別の中粒度バンドル。WD2に従って却下。
- 2つのプラグインのインラインマージ(例:
rigor-dry-schema-validation) — WD2に従い、ウォーカーコードが真に重複する場合のみ許可される;デフォルトはgemごとのまま。 - 個々のプラグインが存在する前に
rigor-dry-rbアンブレラを先行出荷する — 却下;アンブレラは利便性であり、前提条件ではない。WD4に従う。
未解決の問い
Section titled “未解決の問い”dry-railsアダプタの取り扱い。dry-railsはdry-*をRailsに配線する;rigor-dry-railsを別途必要とするか、それともrigor-rails(計画中のメタgem)がそれを吸収するか? 決定はrigor-railsが着地するときに先送り。- dry-monadsの
Result[T, E]キャリア。Rigor::Type::*内の忠実なResult / Maybeキャリア(carrier)があれば、rigor-dry-monadsが.success?/.failure?述語に正確なナローイング(narrowing)を貢献できる。今日のプラグインはflow_contribution_forがDynamic[T]タグ付き和を返すことで貢献している。キャリア導入は別個のADR(ADR-3の修正)であり、ここではスコープ外。
- 2026-05-16 — 初回提案 + 受諾、dry-rbプラグインファミリーのgemごと + メタアンブレラを確定。v0.1.5リリース後のv0.1.6サイクルスコーピング議論が発端。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.