■ 1. 概要と背景
- バクラクは複数プロダクト(申請・経費精算、ビジネスカード、請求書発行、勤怠など)が1つのアカウント基盤上で動作している
- Goの型システム(Defined Type)とコード生成を活用した権限管理の仕組みを紹介する記事
- Protocol Buffersの1ファイルをSingle Source of Truthとして、900個近い権限定数を自動生成する
■ 2. 権限管理の課題
- 管理対象は「複数プロダクト × プロダクトごとの複数ロール × ロールが持つ複数の権限」という3階層の掛け算
- プロダクト増加に伴い、ロールと権限の組み合わせも爆発的に増加する
- 権限名を文字列で管理すると、タイポや存在しない権限名がコンパイルを通り抜け、実行時まで検知できない
- AIコーディングエージェントによる実装速度の向上に伴い、ハルシネーションによるミスがレビューをすり抜けるリスクも存在する
■ 3. GoのDefined Typeによる型安全
- GoのDefined Typeは既存の型をもとに新しい型を定義する仕組み
- PermissionとProductRoleはどちらもint32が実体だが、Go型システム上では互換性のない別の型として扱われる
- Permissionを入れるべき場所にProductRoleを渡すとコンパイルエラーになる
- 生のint32をそのまま渡してもコンパイルエラーになる
- ビルド段階で間違いを検知できる
■ 4. Protocol BuffersをSingle Source of Truthとする仕組み
- 権限管理に関わる定義をすべて1つのpermission.protoに集約する
- 人間が手で書くのはこのファイルのみで、あとはコード生成に委ねる
- permission.protoの構成:
- enum ProductRole(ユーザーのロール)
- enum Permission(個々の操作権限)
- ロール→権限の対応(カスタムオプションで宣言)
- RPC→必要な権限の対応(カスタムオプションで宣言)
- プロダクトごとに番号帯を割り当てることで、新規プロダクト追加時の番号衝突を回避する
■ 5. コード生成の仕組み
- 標準プラグイン(protoc-gen-go): Defined Typeと定数(permission.pb.go)を生成する
- 自社製プラグインの生成物:
- 逆引きマップ(ロール→権限の正引きから、権限→ロールの逆引きマップを生成)
- RPC→権限マップ(Interceptor用)
- ロールと権限の対応はprotoのカスタムオプション(attached_permissions)に宣言的に記述する
- 生成される逆引きマップのキーはPermission型、値のキーはProductRole型であり、Defined Typeの型安全性が生成コード全体に適用される
■ 6. 権限チェックの実装
- ロールの特定:
- クライアントからのリクエストの認証情報をもとに内部通信用トークンを生成する
- トークン内にユーザーのProductRole一覧を格納し、サービス間通信で伝播させる
- Interceptorによる権限チェック:
- Connect RPCのInterceptorにより、すべてのRPCを自動的に保護する
- RPCのフルパスをキーにpermissionByRpcFullNameを参照し権限を検証する
- 開発者はprotoに宣言を書くだけで、個々のハンドラに権限チェックコードを書く必要がない
- HasPermission関数による照合:
- 認証トークンのロール一覧と逆引きマップを突き合わせる
- 引数・マップ・トークンがすべて同じDefined Typeで統一されているため、型の取り違えはコンパイルエラーで検知する
- InterceptorだけでなくビジネスロジックやHTTPハンドラでも同じ型・同じ関数を使用できる
■ 7. 型安全性がもたらす効果
- 存在しない権限の参照: 実行時エラー(サイレントに通過する場合もある)→ ビルド失敗(コンパイル時に検知)
- タイポ: 実行時まで検知不能 → コンパイルエラー
- ロールと権限の対応変更: 複数ファイルを手動修正 → protoを1行修正して再生成で全ファイルに反映
- 新規プロダクト追加: 対応表を複数箇所に追加 → protoに番号帯を割り当ててenumを追加するだけ
- AIによるハルシネーション(実在しない権限名の生成)も同じ仕組みでコンパイルエラーとして防止できる
■ 8. まとめ
- Defined Type × コード生成により、組み合わせ爆発する権限管理を型安全に管理できる
- Single Source of TruthはPermission.protoの1ファイル
- 認証トークン、生成マップ、ビジネスロジックを同じDefined Typeで統一することで、一貫した権限チェックが実現する
- 同様のアプローチは権限管理に限らず、似た構造を持つ様々な領域に応用可能