への継続考察みたいな感じです。
ログインユーザーの持つ役割(権限)を どうやって 振る舞い(ユースケース・サービス)で表現したら良いのかな?という考察です。
権限と役割の文脈だと、アクター、ロール、パーミッション など、いくつかの表現で示されているように思います。
今回は、実装を踏まえつつ、自分なりに整理をすることで、実装(設計)において迷いを持たないようにすることを目的とした考察とも言えます。
アクター(辞書的な(UML的な)ところ)
機能を利用するユーザや、システムが使用するハードウェア、外部システムがアクターとして表現されます。 アクターはシステムの一部ではありません。
ドメインそのものにならないようにする、というのが大事なところという理解です。
やりがちなところとしては、アクターとしての「利用者」があったとして、それを ドメインとして採用すると 全ての集約のルートが 「利用者」になってしまい、巨大な集約をモデリングしてしまうという感じと言えば良いでしょうか?
アクターの権限により起こり得るユースケースから導出される動詞とかコトをモデリングするようにしたら、多分大丈夫です。
ロールと権限
業務システムにおけるロールベースアクセス制御 - Qiita
色々と参考にする中で、やっぱり いつも この記事に戻ってきます。
ザックリいうと、アクセス制御は 権限で指定しましょう、ということになるという理解です。
じゃぁ、ロールは? というと、これもザックリいうと ラベルとかグループとか分類とか、そういうものなので アクセス制限とは 直接的には関係を持たせない方が良い、ということになります。
アクターとロール/権限
印象としては、アクターとロールは 意図しているものは同じように思います。
ちなみに、私は、同じだと考えていました。
そして整理が出来ず悩んでしまいました。*1
Serviceの実装を ユースケース記述のように、アクターとユースケース(振る舞い)によって実装すると、意図の伝わるドキュメントのような実装になると考えていたからです。
アクターとロールが同じという整理では、ロールによるアクセス制限で起こり得る課題を避けられないことになります。
ということで、もう少し 考えてみる必要がありそうです。
先のブログにも 書かれていたことを参考に あれこれ考えてみます。
別途ロールを設計し、組織とマッピングするようにしましょう
ロールという業務的権限の集合
アクターはユースケースの主語なので、業務的権限ではありません。
この文脈では ロールという表現がしっくりします。
権限制御したいオペレーションをグルーピングして、権限として設計するようにしましょう
ここでグルーピングしたものは ユースケースに近しいように考えます。
この文脈では アクターという表現がしっくりします。
ふむ。。。
- ロールはラベル(システムにおけるアクセス権限に使わない)
- 権限はユースケース(もしくはグルーピングしたオペレーション)
- アクターは主語(ロールは システムにおける主語にはならない)
というので、どうでしょうか?
もう少し考えてみます。
ロールはラベル
ラベルはシステムに柔軟さを与えてくれます。
ラベルによる柔軟さ とは、例えば ユーザー登録をする際に 付与する区分として使って、また権限を割り充てた新たに追加も出来るということです。
ロールはラベルとして任意に編集できます(柔軟さの提供)。
権限はユースケース
ユースケースのサイズはアクターから見て、1つのユースケースを終了すれば目的が達成されており、かつ1つのユースケースの中では中断が行われないことを目安にします
とあります。
権限制御したいオペレーションをグルーピングとして、ユースケースは良さそうです。
また、今回の整理とは直接関係は無いのですが、サービスとユースケースを明確に分けて整理できるところも良さそうです。
ユースケースはシステムが提供する価値なので、ラベルのように編集するものではなく、システムの価値として定義(設計)します。
アクターは主語
アクターはシステムの利用者(外部)として ユースケースの主語として表現します。
アクターもユースケースと同様に 編集するものではなく、定義(設計)するものです。 こちらも、機能に対する要件の厳密さを表現します。
さらなる考察
自分なりに 納得ができる整理が出来てきつつあります。
では、もう少し 具体的に実装を踏まえた考察を進めていこうと思います。
コードによる表現対象
ロールはラベルなので、コードによる静的な表現対象から外します。
ユースケース(という名前の権限のグルーピング)と、アクター(という名前のユースケースの関連)が実装(つまり定義)の対象になります。
レイヤー(パッケージ)
結論:アプリケーション層
当初、アクターはシステムの外を定義したものなので、全てのレイヤーから参照できうる層として、ドメイン層が良いかな?と考えました。 ですが、アクターは、
ということで、アプリケーション層配下に配置するのが妥当だろうと考えました。
またアプリケーション層であれば、プレゼンテーション層から参照できる、ドメイン層から参照できない という制約も 目的に合っていると思います。
アクター
アクターの種別は、Enumで実装したいです。
ただ、Enumそのものは 継承が使えません。
UMLの表現としてアクターは 汎化や 包含を 用います。
インターフェースを使ったら、表現できうる実装になりそうな気もしますが、そうすると アノテーションで指定しづらくなるような気もするし。。
とはいえ、権限が主たる関心事であると割り切れば、あとは差分の表現さえできれば十分な気もします。 実際、UMLによる汎化と包含について「これが正しい」という厳密な基準について言及されている記事を見つけることも出来ませんでした。 また、そこの厳密さの基準を設けても、自分自身 その基準が妥当なのか 数日経てば変わるような感覚もあり、とりあえず 何かしらの方法で差分表現ができるような実装が出来ればよいだろう、という結論に至りました。
ユースケース(権限)
こちらも Enumで実装したいです。
権限のグルーピングとしてユースケースという表現を考えていますが、ユースケースもアクターと同じく 差分の表現(汎化や包含)があります。
ですが、今回の主たる関心事は ユースケースを実現するフローの差分ではなく、アクターとユースケースの関連のなので、アクターであったような課題は 考える必要は無さそうに思います。
実装
アクター、ユースケース(権限) どちらとも Enumで実装します。
クラス名を、Actor、Usecase として 詳細を列挙子として実装し、アプリケーション層のルートパッケージ直下に配置します。
アクターとユースケース(権限)
関連を示すクラスを別途設けるのではなく、アクターの要素として定義します。
サービス
クラスアノテーションとして、サービスの主語として アクターを宣言します。
アクターは複数指定することも可能です。
こうすることで、ユースケース記述に近しいものを サービスの実装で表現します。
ログインユーザーの権限と アクターに関連付けられた ユースケース(権限)を 用いて、当該サービスの実行可否を判定します。
アクセス制限の指定として、サービスはユースケースを構築する振る舞い(つまり部品)ということで ユースケースを使うことも想定できます。 もちろん「あるサービスは、あるユースケースの部分である」という実装表現もあるとは思いますが、アクターを主語として サービスを述語とする 実装と比べてみて、 実装から理解できる文脈において 優位性を感じることが出来ませんでした。
したがって サービスにおけるアクセス制限は、アクターだけにします。
コントローラーでのアクセス制限
アクセス制限は、システムの中と外の境界となる コントローラーにも設けることも想定されます。 実際、これまで私もアクセス制限を設けるとしたら、コントローラーに実装していました。
ですが、サービスをユースケース記述のように実装する、という考えから、アクセス制限に相当するものは サービスにおけるアクターで表現した方が分かりやすいという考えに変わりました。
とすると、コントローラーで行うアクセス制限は どうするのが良いでしょうか?
結論としては、コントローラーのアクセス制限は ユースケース(権限)で行うです。
理由は、操作は ユースケースの部分である、という文脈で実装をしたいからです。
呼び出すサービスを振り分ける場合も権限(ユースケース)を用いて 分岐をします。*2
また、アクターの使用を避ける理由として、アクターは条件分岐に使えない、ということもあります。
アクターは分岐条件に使えない
少し、この点についての考察を追記しておきたいと思います。
ログインしたユーザー情報から、把握できるのは ロールおよび、それに紐づく権限(ユースケース)だけです。
アクターの代替としてロールは使えません。 ロールは、アクターと異なり、業務的権限の集合のような 全く違う基準で決めるもので あくまでラベル(分類)です。
では、権限(ユースケース)からアクターを導出するというのはどうでしょうか。
例えば、ロールに関連付けられた 権限(ユースケース)が、アクターのユースケース(権限)と完全一致していたら、目的のアクターであると見做せるでしょうか?
それは無理があります。
述語の集合が一致しているからといって、主語は特定できません。犬は走りますが、走れたら犬である とはなりません。
ということで、導出できないアクターを、コントローラーで 呼び出すサービスを分岐させるような処理をしたい場合の条件として使うことは出来ない、ということになります。
アクセス制限が無かったら?
使用用途としては、アクセス制限のための定義なので、特に そのような制限が無ければ、アクターとユースケース(権限)の実装は不要かもしれません。
ただ、私としては 要件として定義した時点で、コードに落としておいて 制限が必要になったら使用するというのでも良いかな?と思っています。
まとめ
参考資料
Bounded Context パターンの実践方法は? | システム設計日記
https://qiita.com/suke_masa/items/1473450a8b59eea5f3cd
さいごに
うーん、、もったいぶった割に当たり前のことを書いただけになった気がしなくもないですが、私としては スッキリしました。
アクターって、システム境界を決めるために大切である、ということは 周知の事実だと思うけど、それをコードとしてどうやって表現するのか?っていうのは あんまり見ないなぁ、と思ったり思わなかったり。
そもそもシステムの外部なのだから コードで実装するものではない、ということなのかなぁ?
でも、何を外部とするのか、外部をどう定義して どう捌くのか、っていうのは システムの内側で それなりに判別しないといけないと思うんですけど、どうなんですかね?
さて、次は この整理を踏まえて実装例を まとめようかな。