どうも、いからしです。

いつもこのブログでは割と具体的なプログラミング手法とか仕組みについて触れることが多いんですが、毎回説明資料の用意が面倒くさいのでたまには気分を変えて抽象的な設計のお話をすることにします。

タイトルにもある通り今回は権限(ロール)関連のデータ設計がテーマです。

「XXという機能は管理者だけにアクセスを許可したい」みたいなアレの話。想定する読者は、あまりデータ設計の経験がない若手エンジニアです。

 

あ、ちなみにタイトルの元ネタ、皆さんご存知ですかね?弊社も若い社員の方々だともうピンと来ないかもしれない。

 

まずはアンチパターン

あるべき論から説明するよりも、アンチパターンとその問題点から解決方法を導く方がスムーズな気がするので、まずは稀によく見かけるアンチパターンを説明します。

また、このパターンを私自身も若手の頃にやってしまったことがあると先に白状しておきます。過ちは誰にでもあるのです。

 

題材として、ある商品管理システムに下記のような要件があるとしましょう。

  • 機能は下記の3つが存在する。
    • 店舗管理機能
    • スタッフ管理機能
    • 在庫数管理機能
  • 各スタッフは下記3つの権限のいずれかを保有する。
    • 全体管理者
    • 店舗管理者
    • 一般スタッフ
  • 各権限毎にアクセス可能な機能は下表の通りである。
全体管理者 店舗管理者 一般スタッフ
店舗管理機能 × ×
スタッフ管理機能 ×
在庫管理機能

 

今回は「機能にアクセスできる権限の仕組みをどう実現するか」という話なので、各機能の具体的な内容については触れませんし気にしなくて大丈夫です。

 

では次にこの要件を満たすデータ設計のアンチパターンですが、それは一言でいうなら権限のハードコーディングパターンです。

例えば、各スタッフの情報を保持するstaffsテーブルがあるとします。

カラム名
id number
name varchar(255)
role_type varchar(10)

role_typeカラムには「entire_admin」「store_admin」「general」のような値がセットされます。

権限のハードコーディングパターンでは、アクセスしてきたスタッフのrole_typeを元に、プログラム側で下記のような条件分岐を行います(今回はJavaScriptで書きます)。

このパターンの何が問題なのかというと、保守性・拡張性の低さです。

皆さんご存知のとおり、システムというのは初回のリリース以降、ユーザーからの要望や業務戦略など、様々な理由で機能改修や拡張が行われます。

例えば「店舗管理者は在庫管理を行わないので、思い切って在庫管理機能へのアクセスを不可としよう♪」みたいな改修が必要となった場合、このやり方だと条件分岐のプログラムを修正して、各サーバーへリリースするという作業が発生します。

今回の例題は機能数が少ないシンプルなものなのでよいですが、一般的な業務アプリケーションには数十〜数百の機能があり、それらへのアクセス権限が様々な事情でコロコロ変更されるようなことは日常的に起こりえます。

その度にプログラム修正とリリース作業が発生するのは、控えめに言っても軽めの拷問です。保守部隊の離職率が上昇します。

 

また、「スタッフ管理機能のうち、一部の機能は一般スタッフでもアクセス可能としたい」というような仕様変更もよくあります。

そのような細かいアクセス制御に引きずられてプログラムの条件分岐がどんどん複雑になっていき、ビジネスロジック以外の部分に単体テストの工数を無駄に取られてしまいます。開発部隊の離職率が上昇します。

 

ということで、よほど小規模なアプリケーションでない限りは、このような権限のハードコーディングは避けるべきです。

 

ではどうするのか

と言うと、ハードコーディングを避けて権限の設定をDBで管理します。

やり方は何パターンかあるのですが、まずは一番シンプルな方法を紹介します。登場するテーブルは4つです。

 

■ 役職情報を保持するpositionsテーブル

カラム名
id number
name varchar(255)

■ スタッフ情報を保持するstaffsテーブル

カラム名
id number
name varchar(255)
position_id number

■ 役割(≒権限)情報を保持するrolesテーブル

カラム名
id number
name varchar(255)

■ どの役職がどの役割を持つのかを管理するposition_has_rolesテーブル

カラム名
id number
position_id number
role_id number

また、各テーブルの関連は下記の通りです。

今回の要件を満たすように各テーブルにデータを投入すると、下記のような形になります。

■ positionsテーブル

id name
1 全体管理者
2 店舗管理者
3 一般スタッフ

■ rolesテーブル

id name
1 店舗管理
2 スタッフ管理
3 在庫管理

■ position_has_rolesテーブル

id position_id role_id
1 1 1
2 1 2
3 1 3
4 2 2
5 2 3
6 3 3

あとは各機能の処理開始前に、アクセスしてきたスタッフのposition_idから利用可能なrole_idの一覧を取得し、今回アクセスしようとしている機能とマッチするrole_idがその中に存在するかを確認します。

この処理は当然ビジネスロジックとしてではなく、AOP的に実装されるべきです(フレームワークのfilter機能とか)。

現実的には個別の機能filterに同じようなチェックを実装するのではなく、各機能とrole_idのペアはDBや設定ファイルなどで定義・管理し、全機能の共通AOPとして上記の処理を実装すべきでしょう。

 

この方法であれば、「ある役職の権限を変更したい」はもちろんのこと、「ある機能の一部をある権限にも解放したい」と言うようなケースもDBの変更だけで対応することができます。

例えば前述の「スタッフ管理機能のうちの一部の機能は一般スタッフでもアクセス可能としたい」であれば、下記の方法で対応が可能です。

  • rolesテーブルのスタッフ管理機能(id = 2)を、一般スタッフでもアクセス可能な機能(id = 4)と、そうでない機能(id = 2のまま)に分ける。
  • position_has_rolesテーブルに、role_id = 4 & position_id = 3のレコードを追加する。
  • 機能とrole_idのペア設定もそれに合わせて更新する。

 

ポイント

アンチパターンと修正後のパターンで大きく違うポイントは、役職と役割(≒権限)という異なる2つの要素を、きちんと異なるものとして扱えているか否かです。

アンチパターンの場合はこれをうまく分離できていなかったので、その分離をプログラム側で行う必要がありました。

今回の件に限らず(そしてデータ設計に限らず)、1つのモノに複数の意味を持たせようとすると、設計や実装が複雑になり保守性・拡張性が下がる可能性が高くなります。

なので設計であれ実装であれ、今自分が作っているものやレビューするものが単一責任原則を満たしているかどうか、常に念頭におくように心がけましょう。

 

権限モデリング

権限をどう定義するかという方法(権限モデリング)は、今紹介した以外にもいくつかの方法があります。

最後にそれらの権限モデリング手法から代表的なものを3つを紹介します。

 

ACL(Access Control List)

アクセスを制御したいリソースに対して、「この操作を許可する(しない)対象の一覧」を定義する方法です。

先ほどの商品管理システムの例題で言うなら、staffsテーブルと1:多の関係になるstaff_authorizationsテーブルのようなものを作り、対象のスタッフに許可する機能の数だけレコードを追加するようなイメージです。「全体管理者」のような機能がなくなるので要件を満たせなくはなりますが。

 

もっとわかりやすい例でいうと、アクセスを許可するIPアドレスのホワイトリストなどがコレに当たります。

シンプルな設定でできるのですが、役職単位のようなグループによる設定ができないため、管理が面倒だったり柔軟性が低かったりします。

 

RBAC(Role Based Access Control)

アクセスを制御したいリソースに対して、「この操作を許可する(しない)ロール」を定義し、ユーザーにロールを与えることで権限を付与する方法です。

前述の商品管理システムの要件自体も思想的にはRBACです。

なのでアンチパターンの方もRBACを実現してはいます。

世の中の権限機能があるアプリケーションは、コレを採用しているケースが大半だと思います。

 

ABAC(Attribute Based Access Control)

RBACに「属性」という概念を追加した考え方です。

これまた商品管理システムを例にとると、「一般スタッフであっても、ある特定の店舗に所属している人であればスタッフ管理機能にアクセスできる」のような感じ。

「ある特定の店舗に所属している」という属性まで意識したモデルです。

できることが細かく柔軟性が高い手法ですが、その反面設計や実装が複雑になり、かつユーザー側のシステム理解ハードルも上がります。

 

 

はい。見てもらってわかると思うんですが、これを選べばOK!みたいなものはありません。

ケースバイケースで適切なモデリングを選び、選んだ思想に沿って適切な設計を行う必要があります。

 

ある程度成熟した既存システムのプロジェクトに参加すると、この辺りの詳しい仕組みを意識することのないままビジネスロジックを触り続けることも多いと思うんですが、せっかく今うまく動いているものがあるのであれば、ちょっと興味を持って仕組みを調べてみるといいかもしれません。

ではまた。