タラバガニー設計局stalins.clubNOTE/notes/tanukirpc-route-with-transformer

tanukirpc の RouteWithTransformer

github.com/mackee/tanukirpc は DI コンテナ (Registry) を Context[Reg] として持ち回る自作 Web フレームワークである。API は基本的に *Router[Reg] のメソッドとして提供され、すべて「同じ Reg の世界」で完結する。

type Reg struct{ DB *sql.DB }
func (r *Router[Reg]) Get(pattern string, h Handler[Reg])
func (r *Router[Reg]) Route(pattern string, fn func(r *Router[Reg])) *Router[Reg]

Router[Reg] の API は16個あり、そのうち15個はメソッドとして書けている。唯一メソッドにできなかったのが RouteWithTransformer で、これはパスの配下だけ Reg を別の型 (例えば Reg2 = struct{ *Reg; Task *Task }) に差し替えるための API である。

func RouteWithTransformer[Reg1 any, Reg2 any](
	r *Router[Reg1], tr Transformer[Reg1, Reg2], pattern string,
	fn func(r *Router[Reg2]),
) *Router[Reg1]

変換先の Reg2 はレシーバ (*Router[Reg1]) のどこにも書かれていない。つまり「レシーバ由来では決まらない型パラメータ」が要るので、Go 1.26 まではパッケージレベル関数にするしかなかった (メソッドはレシーバを持つ関数)。ジェネリクスメソッド (Go 1.27) が入った Go 1.27 なら、この Reg2 をメソッド自身の型パラメータとして宣言できる。

func (r *Router[Reg1]) RouteWithTransformer[Reg2 any](
	tr Transformer[Reg1, Reg2], pattern string,
	fn func(r *Router[Reg2]),
) *Router[Reg1]

メソッド化の実利は「統一感」ではなく、router. と打てば補完候補に出てくることにある。パッケージレベル関数のままだと存在に気づかれないまま埋もれるが、メソッドになれば残り15個と同じ場所から発見できる。

正確に言うと、メソッドにできなかったのは「*Router[Reg1] のメソッドにすること」であって、Transformer[Reg1, Reg2] 側のメソッドにすれば Reg1 / Reg2 はどちらもレシーバ由来になるので Go 1.26 でも書けていた。できなかったのは API の主語を Router に揃えることだけである。

また、既存の RouteWithTransformer に対してこの変更をそのまま当てはめるのは破壊的変更になりうる (既存メソッドへの型パラメータ後付けは破壊的変更)。メソッド化は既存 API に自由に適用できる書き換えではない。

メソッド化するなら一緒に直す必要がある箇所も、実際にコードを読んで複数見つかっている (main、commit 0c98071 時点)。まず静的解析側の genclient/gendoc.gotryRouteWithTransformer は SSA 引数がちょうど5個であることと pattern が文字列リテラルであることを前提にしており、メソッド化で引数が1個減れば確実に壊れる。また RouteWithTransformer の内側 Router[Reg2] は構造体リテラルで手組みされていて clone() を通らないため defaultMiddleware フィールドがコピーされない。closer の実行タイミングにも2系統あり (NewContextHookFactory 経由は DeferDoTimingAfterResponseRouteWithTransformer 経由は DeferDoTimingBeforeCheckError)、この差はコードにも README にも書かれていない。Transformer に関するテストは1件もなく、README が主張する「defer が context 境界を跨いで LIFO で動く」という挙動を検証するテストが存在しない。

最も優先度が高いのは godoc の欠如である。context.goContext[Reg] / Transformer / NewTransformer / Defer はコメントが1行もない。同じリポジトリの auth/oidccodec/inertiajs には全公開シンボルに doc があるので、Transformer 周りだけが集中的に無コメントという偏りになっている。API の使われやすさに godoc がどれだけ効くかは別の実験で確かめてあり、そちらの結論は AI に API を使わせたのは godoc だけだった にまとめてある。実際、作者本人が書いた _example でも Transformer を使っている例は _example/todo/main.go の1箇所だけで、他のハンドラは存在チェックを都度コピペしている。

RouteWithTransformer をリソースベース認可へ使うと、単なる Registry 変換ではなく「認可に成功したリソースだけを下位ハンドラへ渡す」境界として扱える。この設計上の意味は 認可済みリソースをハンドラ入力にすると前提を構造化できる、ルートのネストと権限のネストを対応させる場合の利点と限界は パス階層を認可階層として使う に分けて整理した。周辺パターン全体の入口は Webアプリのリソース認可パターン 調査ノート

ただし、この構造をそのまま object-capability と呼ぶのは強すぎる。object-capability では authority が参照の保持・到達可能性によって制約される (Object-Capability は authority を参照の到達可能性で表す)。一方、典型例の Reg2 = struct{ *Reg; Task *Task } のように元の Registry を埋め込み、そこに汎用 DB / repository が残るなら、下位 handler は認可済み Task を受け取りながら別 resource を取り直せる。したがって現状の RouteWithTransformer は、より正確には 認可済み resource/evidence を request-local dependency graph に追加できる機構 と位置づけるべきである。Authorized<Resource> 型との関係と限界は 「認可済みリソース型」は既存研究の直訳ではなく設計上の合成、関連研究全体は 認可をプログラム構造で表す研究 調査ノート に整理した。

出典

  • Go Release Party 1.27 の発表資料リポジトリ slide/deck.md (tanukirpc / the-odd-one / now-method スライド)
  • 同リポジトリ memo.md §3
  • github.com/mackee/tanukirpc (main, commit 0c98071 時点)
  • The Joe-E Subset of Java, USENIX Security 2006

▸ ノート一覧に戻る