GoバックエンドにRegoを導入して失敗した話
2026/08/24 / 2026/08/24
Goで開発しているバックエンドへ、認可ポリシーを記述するためにRegoを導入した。
先に結論を書くと、このプロジェクトにRegoを本採用したことは、私が行った技術選定の中でも最大級の失敗だった。
ただし、Regoそのものが悪いわけではない。
失敗だったのは、認可要件がRegoに適しているか、そしてチームがRegoを使った設計を維持できるかを十分に確認しないまま、アーキテクチャの中心へ組み込んだことである。
目次
[非表示]
Regoを導入した理由
このシステムには複数のロールが存在し、ロールごとに操作可能なリソースと範囲が異なっていた。
認可条件には、次のようなものがあった。
- 対象リソースを作成・閲覧・更新・削除できるか
- 自分が所有するデータだけを操作できるか
- 同じ校舎に所属するデータだけを閲覧できるか
- 同じグループに所属するか
- 対象ユーザーがどのロールか
- 契約に対応する認可キーを持っているか
これらをGoの各ユースケースへ直接書くと、認可ロジックが分散すると考えた。
そこで、業務ロジックをGo、認可ポリシーをRegoへ分離しようとした。
将来的にはバックエンドだけでなく、フロントエンドやCMSでも共通のポリシーを利用し、画面や操作の可視性を制御する構想もあった。
構想だけを見れば、Regoを検討したこと自体は不自然ではなかったと思う。
実際には分離できなかった
実装後の認可処理は、おおむね次のようになった。
- RegoがロールごとのCRUD可否を返す
- Goがロール・リソース・操作から
ownやschoolなどの条件を決める - Goが対象データを取得する
- Goが所有者IDや校舎IDを比較する
- 一覧取得ではRepositoryやUsecaseが検索条件を追加する
Regoが返していたのは、最終的な認可結果ではなかった。
たとえば、Rego上では、あるロールがグループを作成・閲覧・更新・削除できると定義されている。一方で、実際には「自分が作成したグループのみ」「所属校舎のグループのみ」といった制限がGo側に存在する。
Regoだけを読んでも、実際にどのデータへアクセスできるか分からない。
Goだけを読んでも、そもそもその操作が許可されているか分からない。
認可ポリシーを分離したのではなく、一つの認可判断をRegoとGoへ分断してしまった。
複雑さの種類を見誤った
認可が複雑であれば、Regoが適しているとは限らない。
今回の認可で複雑だったのは、ロールごとのCRUD表よりも、業務データとの関係だった。
「自分のデータか」「同じ校舎か」「契約対象か」といった条件を判断するには、MongoDBから対象データを取得しなければならない。
一覧検索では、取得した全データをRegoで一件ずつ判定するより、校舎IDなどをMongoDBの検索条件へ組み込む方が適切である。
つまり、認可の中心部分がGoのドメインモデルやRepositoryと密接に結びついていた。
このような認可を無理にRegoへ分離しようとすると、Go側にはデータ取得、入力変換、検索条件、後処理が残る。結果として認可ロジックは消えず、Regoとの境界だけが増える。
複数システムで使う構想も実現できなかった
Regoを採用した理由の一つには、フロントエンドやCMSでも同じ認可ポリシーを利用する構想があった。
しかし、複数システムで共有したいものが何なのかを、十分に分けて考えられていなかった。
バックエンドが次のような結果を返し、フロントエンドがボタンの表示制御に使うだけなら、Regoは必要ない。
{
"read": true,
"write": false,
"delete": false
}
Goで判定した結果を返せばよい。
Regoが有効になるのは、複数のサービスが同じポリシーを独立して評価する場合である。
そのためには、少なくとも次の設計が必要になる。
- 共通の入力スキーマ
- 共通の判定結果
- ポリシーのバージョン管理
- ポリシーの配布方法
- 各システムでの評価方法
- 互換性とロールバック
- ポリシーの変更責任者
今回のシステムでは、この基盤を作る前にRegoを本採用してしまった。
しかも、最終的なownやschoolの条件はGo側に存在した。そのため、RegoだけをフロントエンドやCMSへ配布しても、実際の認可仕様を再現できない状態になった。
技術だけでなく、チームにも適合していなかった
もう一つ見落としていたのが、チームがRegoを維持できるかという問題だった。
Regoを導入するなら、チームには次の能力が必要になる。
- GoとRegoの両方を読める
- 業務ロジックとポリシーの境界を判断できる
- Regoの入力と出力を設計できる
- ポリシーとGoの二重定義をレビューで防げる
- 認可処理全体を横断して確認できる
当初の構想を理解している人が一人しかいない状態では、チームとしてその技術を採用できているとは言えない。
実際、私は途中から認可設計へ継続的に関与できなくなった。設計の主体が変わった後、Regoで完結しない条件がGo側へ追加され、認可の責務が複数のレイヤーへ広がっていった。
ただし、「私が設計を続けていれば成功した」と断定するつもりはない。
一覧検索や校舎スコープなど、元からRegoだけでは扱いにくい要件が存在した。私が関与していたとしても、別の形でGoとの境界に苦しんだ可能性はある。
問題は、特定の人が外れただけで設計思想を維持できなくなる技術を、チームの基盤として採用したことである。
今ならどうするか
今なら、認可処理をGoの型付きコードとして実装する。
たとえば、ロールと対象リソースから権限を返すAuthorizerを用意する。
type Permission struct {
CanCreate bool
CanRead bool
CanWrite bool
CanDelete bool
Scope Scope
}
type Authorizer interface {
GroupPermission(
actor Actor,
target GroupTarget,
) Permission
}
ScopeにはOwn、School、Group、Allなどを定義する。
一覧取得では、ScopeをRepositoryの検索条件へ変換する。単体操作では、取得したEntityに対して最終的な許可・拒否を判定する。
フロントエンドやCMSには、Goが返したPermissionを渡して可視性を制御する。
これなら、
- Goの型検査が使える
- IDEで参照関係を追える
- リファクタリングしやすい
- 認可処理をGoのテストで完結できる
- Rego用の入力変換が不要になる
- 実際の検索条件と認可条件を近くに置ける
という利点がある。
将来、本当に複数サービスが同じポリシーを独立して評価する必要が生じた時点で、Regoや外部認可基盤を改めて検討すればよい。
Regoを採用してよい条件
今回の経験から、少なくとも次の条件が揃わなければRegoを本採用しない。
- 複数のサービスが同じポリシーを評価する
- ポリシーをアプリケーションと独立して配布する
- ポリシー変更とアプリケーションのデプロイを分離する
- Regoが最終的な認可判断を返す
- 入力・出力スキーマが定義されている
- 複数人がRegoを保守できる
- ポリシーのレビュー責任者が決まっている
- Go側へのポリシー重複を検知できる
「将来使うかもしれない」だけでは採用しない。
将来の構想は、現在の複雑性を正当化しない。
結論
Regoを導入すれば、業務ロジックと認可ポリシーが自然に分離されるわけではない。
分離可能な境界、共通の入出力、配布方法、運用責任が先に存在し、その境界をチームが継続的に維持できる場合に、Regoは有効になる。
今回のプロジェクトでは、技術要件とチーム要件の両方を見誤った。
認可の複雑さは業務データと検索処理に密接に結びついており、Regoへ綺麗に分離できる種類ではなかった。さらに、その難しい境界を維持できるチーム構成や設計体制もなかった。
Regoが高機能であることと、そのプロジェクトへ導入すべきことは別である。
私の最大の失敗はRegoを知ったことでも、検討したことでもない。
Regoが成立する条件を確認しないまま、本採用したことである。