記事のサマリー(TL;DR)
- freeeは複数プロダクト共通で利用できるアクセス制御システム「権限管理基盤」(Go言語製マイクロサービス群、XACMLベースの設計、約5年前から実装)を内製しているが、freee会計など基盤誕生前から開発されてきたプロダクトは独自の権限管理処理を持っている。
- 権限管理基盤チームは、10年以上の開発history を持つfreee会計の独自アクセス制御のうち、RBAC(Role-Based Access Control)機能に対象を絞り、基盤側へデータを同期するプロジェクトを進めた。
- アーキテクチャはストラングラーフィグパターンを参考にし、「権限データをfreee会計・基盤の両方に書き込む」フェーズをまず完了させ、今後は参照箇所を基盤側へ段階的に置き換える計画。現時点では完全な移行は完了していない。
詳細
背景その1:freeeにおけるアクセス制御・権限管理の基盤とは
freeeは、お金や人の情報といったセンシティブな業務ドメインを扱うため、「管理者ユーザーはすべての機能を利用できる」「一般メンバーは限られた範囲でのreadしかできない」といったアクセス制御が必要不可欠だとしています。freeeは数年前からアクセス制御の基盤化を進めてきました。
権限管理基盤を組み込むメリットとして、記事では以下が挙げられています。
- ユーザー目線:統一された操作体験・設定画面を利用できる
- 社内開発メンバー目線:複雑となりがちな制御処理を自作しなくてよくなり、車輪の再発明を防げる
各プロダクトは基盤導入により、「ユーザーのロールに基づくアクセス制御(RBAC)」や「ユーザーが所属する事業所のライセンスに基づくアクセス制御(LBAC、freeeの独自用語)」などを、設定ファイルや実装追加によって実現できるとしています。freeeで新たに実装されるプロダクトでは、ほぼすべてでこの権限管理基盤が採用されているとのことです。
権限管理基盤は約5年前から実装が始まったもので、XACML(eXtensible Access Control Markup Language)をベースとした設計思想を採用し、アクセス制御判定を行う小さなGo言語製マイクロサービスと、これにアクセスするための共通ライブラリ類、制御設定・定義を投入するための機構で構成されています。
背景その2:freee会計がもつ独自のアクセス制御
freee会計は、freeeの中で最初に実装された事業の根幹となるプロダクトであり、すでに10年以上の開発の歴史があるとされています。権限管理基盤が生まれる前から存在しているため、基盤機能は利用しておらず、freee会計内部に独自実装としてアクセス制御が組み込まれています。
初期は「管理者」「一般(経理)」「閲覧のみ」のような、システム側で定数として定義された決まったロールのみを持つ仕組みでしたが、その後ユーザーが柔軟にロールを定義できる実装も追加され、現在は以下のような複数種別の制御を担う独自のアクセス制御機構となっています。
- ロールに基づくアクセス制御(RBAC):例として、経理担当ロールを持つ人はxxxができる、従業員はyyyしかできない、など
- ライセンスに基づくアクセス制御(LBAC):例として、特定のライセンスでのみ一部機能が利用可能になる、など
- 属性に基づくアクセス制御:例として、「自分が作成した範囲」のデータだけが見られる、「自分が所属するグループの範囲」のデータだけ操作できる、など
アクセス制御機能をマイクロサービスへ移行することのモチベーション
freeeは「統合型経営プラットフォーム」として機能提供することを目標に掲げており、複数プロダクト間でデータが連携される機能が多数実装されています。この実現のため、他プロダクトの権限を参照・チェックしたい場面が発生しますが、freee会計は権限管理基盤を利用できていないため、権限データを参照するためだけに各プロダクトからfreee会計への依存が生じる場面があるとしています。
提供元は、基盤側からデータを取得する方が開発者にとって実装が早くなり、基盤側はGoで作成された小規模なマイクロサービスである分、スケーリングのしやすさやレスポンス速度改善が期待されると説明しています。また、レスポンス速度の改善や操作の安定性はユーザーにとっても安定したfreee利用につながる利点だとしています。
これらの背景から、プロジェクトは「ユーザーに対して統合体験を提供する」ことをモチベーションに掲げ、「freee会計の様々な独自アクセス制御機構から、RBACの機能を基盤側にデータを同期する」ことにスコープを絞って進められました。基盤側にはLBAC制御の機構も実装済みとのことですが、プロジェクトが進まないリスクや機能の優先度を勘案し、今回はRBACのみを対象としています。
補足:過去の類似事例 – freee人事労務での権限管理基盤への移行
freee会計と同じく独自の権限管理機構を持っていたfreee人事労務については、既に基盤移行が完了しているとのことです。この場合も「ユーザーへの統合体験の提供」を主軸に、独自機能から権限管理基盤への移行が進められました。
移行・同期のアーキテクチャ方針
ある程度の規模の既存モノリスが持つ機能を一度に基盤に置き換えることは現実的ではないとし、既存コードベースの複雑さや、事故・障害時のリスクの大きさ・切り戻しの困難さを理由に挙げています。アクセス制御に関わるドメインでは、最悪の場合、全ユーザーがfreee会計のすべての機能にアクセスできなくなる可能性があるとしています。
これらを踏まえ、以下の方針でアーキテクチャ設計が行われました。
- まずは、権限データをfreee会計・基盤の両方に書き込む状態とする
- 権限データの参照・権限判定で、freee会計側を参照している箇所を、少しずつ基盤側に置き換えていく
2については完遂まで長期間になることが予想されるため、今回のプロジェクトでは1のデータ同期をやり遂げ、2のための置き換え準備をすることを主なスコープとしています。アーキテクチャの全景としては、一般にストラングラーフィグパターンと呼ばれるものを参考にしたとのことです。
「権限」を構成する要素を分解し、基盤へデータを同期する計画を立てる
移行するデータは、厳密には以下のように分解されるとしています。
- 権限を構成する項目(あるリソース・機能に対して許可される操作)とプリセットロール
- ユーザーが動的に作成したロール(カスタム権限)
- ユーザーのロール付与情報
これらすべてを移行することで、基盤側からの権限参照と権限判定が初めて実現されるとしています。移行元と移行先ではデータの形式や仕様が揃っていないことが大半であり、各項目ごとに基盤側の表現への落とし込み方を決める必要があるとのことです。基盤チームの全員がfreee会計の既存実装に詳しいわけではないため、移行元の実装に詳しいチームへの相談やレビュー依頼をしながら進めたとしています。
移行するデータによっては基盤側に新機能やデータモデルの追加実装が必要になる場合もあり、今回の事例ではfreee会計側の「属性に基づくアクセス制御」に相当する機能が該当するとしています。これについては、本プロジェクト終了後に基盤側で新たな実装を行い別途同期する判断をしたとのことで、今回の記事では扱われていません。
同期処理を実装する
「権限を構成する項目」と「プリセットロール」を同期する
ロールは「xxxの操作ができる」といった小さな権限項目の集合として表現されます。freee会計側ではRubyのファイル上にHashとして表現されており、基盤側ではymlファイルでの表現となっています。階層構造の違いはあるもののほぼ同じデータ構造であったため、スクリプトによる変換処理の作成のみで移行が実現できたとしています。システムのデフォルトで提供される権限(プリセットロール)も、権限項目と同時に基盤側に投入されます。
このほか、freee会計側で「新たに権限項目が増える」「機能の統廃合で権限項目が減る」といった特殊なケースも考慮が必要だったとしています。既存データのマイグレーションが発生する場合、対象の権限項目参照を先にすべて廃止することや、freee会計側・基盤側のどちらのデータ反映を先に行うべきかを検討する必要があったとのことです。
「カスタム権限」を同期する
freee会計には、事業所ごとにユーザーが独自に定義するロール「カスタム権限」を作成できる機能があります。プリセットロールが製品のコード上で定義されるのに対し、カスタム権限はfreee会計のDBに動的に保存されるデータです。
カスタム権限データの同期にあたっては、分散トランザクション的な実装を避け、ユーザー操作への影響を抑えるため、Transactional Outboxパターンでのデータ同期アーキテクチャを採用したとしています。これにより、既存のカスタム権限すべてのデータと、今後会計側で新たに作成・変更・削除されるデータの継続的な反映(増分データの同期)を実現しているとのことです。
outboxにはProtocol Buffersに変換した状態の、「そのユーザー操作時点でのfreee会計形式のカスタム権限データ」を保存します。このoutboxの増分データをGo製の軽量ワーカーが読み取り、基盤側のgRPCを介してデータを保存する形式です。
過去の類似事例(freee会計の権限を部分的に分割して基盤側に移行するプロジェクト)ではRuby製のワーカーを使い、既存のfreee会計内部で利用されているコード・データモデルをそのまま利用できるアーキテクチャとしていたとのことですが、当初はこの過去の機構をそのまま流用する案も検討した上で、最終的にはProtocol Buffersで権限のsnapshotを保存・送信するパターンを採用したとしています。
freee会計形式のデータを基盤側に送るため、基盤形式への変換処理は基盤側で担うことになり、freee会計特有の知識が基盤側に入ってくる一方、freee会計側の実装や同期用ワーカーの処理を簡素化できるメリットがあると判断したとしています。その他の背景として、過去事例のRubyワーカー方式がRuby/Railsに強く依存しておりワーカーのサイズが大きいこと、outboxを利用する基盤用ライブラリが充実しており流用できること、データの同期処理を動かし続ける期間の違い、基盤チームのプロジェクトであるため基盤側に実装を増やす判断がしやすかったことが挙げられています。
反面、Protocol Buffersでのsnapshot的な権限保存形式により設計が難しくなる面もあるとしています。特に削除処理や、基盤側でのバージョニング管理(古いイベントを受け取らないための順序制御や排他制御)には注意が必要だとしています。基盤側で受け取ったレコードの履歴を管理するテーブルを別途保持し、基盤側に反映済みのデータよりも古いデータは受け取らないようにしているとのことです(エラー時の非同期再送制御が入った場合、既に反映済みのデータより古い権限データが基盤側に送られる可能性があるため)。投げられるデータと反映済みデータの新旧比較のため、会計側のロール情報に新たにバージョニング機構を追加する改修も行ったとしています。
非同期でのデータ処理となるため、処理の遅延有無などの監視も必要であり、outboxへの保存から基盤反映までの時間ラグをログとして出力・測定することや、outboxの滞留件数の監視によって実現しているとのことです。
「ユーザーのロール付与情報」を同期する
カスタム権限の移行と同様、outboxパターンによるデータ同期としています。ユーザーへの権限付与情報は、権限管理基盤ではなく、ユーザー・Identity管理用の別のマイクロサービス(ユーザー情報管理基盤)に保存されていますが、実装・管理の都合上、移行データを受ける口は権限管理基盤側に作成しているとのことです。
「権限参照」を移行する
ここまでのデータ同期により、「ユーザーが持つロール情報」と「ロール内の権限情報」の両方が基盤へ移行され、基盤側でのアクセス制御判定が可能になったとしています。freee会計を参照して権限を取得・判定している処理はfreee内の様々な箇所に存在しているため、これらをスムーズに基盤側参照へ移行できるよう、基盤チームは権限参照用のライブラリを新たに作成しました。このライブラリは、権限データ参照のインターフェース統一や各サーバーごとのレスポンス形式の差の吸収に加え、参照切り替え前にデータの差分がないことをチェックする機構(一般にShadow-Testと呼ばれる技法で、社内ではDual-Checkと呼ばれることが多い)も設けているとしています。
プロジェクトの現在地と今後の行方
freee会計については、権限データの基盤側への継続的な同期が完了した段階であり、既存機能すべての載せ替えが完了したわけではないとしています。freee会計の権限を取得・参照する箇所は多数あるため一括の置き換えは困難であり、権限項目ではなくロールの名前を参照して権限判定をしてしまっている箇所もいくつか存在するとのことです。
また、データ同期が非同期であるため、システム全体としては結果整合的なものとなり、強い整合性が必要な箇所は当面の間freee会計側を参照させる制限を設けている状態だとしています。これらを踏まえ、既存の参照処理については今後地道に置き換えていく予定としています。
さいごに
記事では、権限管理基盤への移行プロジェクトのアーキテクチャ・実装の観点が紹介されています。現時点では移行が完全に完了したわけではないものの、既存システムの置き換えやアーキテクチャ設計に取り組む人の参考になれば、としています。
なお、freee会計自体が持つ複雑な仕様や、歴史と経緯による想定外のデータも存在しており、アーキテクチャ検討の試行錯誤やプロジェクトマネジメント上の失敗を繰り返しながら、約1年にわたってプロジェクトを進めてきたと説明されています。2026年10月3日(土)に開催される「freee技術の日」にて、記事著者が本プロジェクトの取り組み姿勢や失敗談について登壇する予定であるとしています。