🌊

DDDにおける認証の実装場所

2022/01/27に公開

こんにちは。株式会社プラハCEO松原です。

DDDに基づいて開発しているアプリケーションの「認証」ってどこで実装するのが良いのだろう?

対象読者

  • 何となくDDDに関する本を読んで理解した気がする
  • 試しにDDDに基づいてアプリケーションを実装し始めた
  • 認証の実装をどこに書くべきかわからず詰まった

結論(オニオンアーキテクチャの場合)

  • 実装はUI層かInfrastructure層
  • 自分ならInfrastructure層
  • 認証後のインターフェースはアプリケーション層
  • コンテキストは1つにまとめている前提(認証コンテキストを作らない場合の置き場所)

UI層って何やねん

DDDとの相性の良さからよく併用されるオニオンアーキテクチャの図を見ると、以下3つの層が一番外側に位置しています:

  • UI(User Interface)
  • Infrastructure
  • Tests


(図はこちらから引用)

UIと言えば、スマホのタッチパネルとか、アプリのボタンもUIと呼ばれますよね。要は ユーザーとサービスの接点 、それがUIです。

WEBサービスのバックエンドであれば、ユーザー(ブラウザ等からのリクエスト)とアプリケーションの接点といえばAPIのエンドポイントですね。MVCに慣れている方であればコントローラー層と近しいものと捉えて差し支えないのではないでしょうか。

Infrastructure層って何やねん

このサービス以外のサービスと疎通するためのコードが書かれる場所 と考えて良いと思います。例えばMySQLとかPostgreSQLを使ってデータを管理する外部DB、認証を行うFirebaseやAuth0などの外部IDaaSなど。

これらは自分が作ったサービスではないので以下のような特徴を意識する必要があります:

  • 自分が認識していないタイミングで変わる可能性がある
  • 別のサービスに切り替える可能性がある

例えばPostgreSQLに依存したコードが自分のサービスのあちこちにとっ散らかってしまうと、いざMySQLに変えたい時に大変ですよね。変更箇所を1箇所にまとめたい。どうせなら何らか意味を持った単位でまとめたい。そんな時に「外部サービスと繋がるコードは全部Infrastructure層にまとめよう」みたいな方針が便利なわけですね。

なんでUI層/Infrastructure層に認証を実装すんねん

消去法で他の層に認証を実装することを考えてみます:

ドメイン層で認証??

そもそもドメイン層を作る目的は ドメイン層内の変更理由を「ビジネスロジックの変更」に限定すること です。例えばDBをMySQLからPostgreSQLに変更する、という技術的な変更理由に起因してドメイン層を変更する必要が生じるのなら、ドメイン層が適切に分割できていないことを意味します。

ドメイン層で認証を行った場合、上記のDBの例と同じようなことが起きます。例えば認証にFirebaseを使っていたところをAuth0に切り替えたとします。ドメイン層に認証を実装していたら、「サードパーティツールを切り替える」という技術的な変更理由に起因してドメイン層のコードを変更する必要が生じます。

ドメイン層より外側の(DBや認証方法など技術的な)変化にドメイン層が引っ張られてはいけないので、ドメイン層に認証を実装するのは違和感がありますよね・・・

アプリケーション層で認証??

「ドメイン層の知識を組み合わせてユースケースを表現する」のがアプリケーション層の責務と考えた場合、アプリケーション層に変更が及ぶのは、アプリケーションのユースケースが増えたり減ったり変わった時、あるいはより内側に位置するドメイン層のインターフェースが変わった時だけのはず。こちらもドメイン層と同じように、DBや認証方法などの技術的な変化に引っ張られてはいけません。

なのでドメイン層と同じ理由で、アプリケーション層に認証を実装するのは違和感があります。

残ったのは一番外側のUI/Infrastructureのどちらか

となると消去法で一番外側のUI,Infrastructure,Testいずれかの中に認証を実装することになりますが、Testはまず除外して、UIかInfrastructureいずれかに実装することになりそうです。

認証方法の変更によってアプリケーション層とドメイン層が影響を受けなければ最低限の問題はクリアできているので、ここまできたらUI/Infraどちらに実装しても構わないように思いますが、自分ならInfrastructure層に実装すると思います。外部認証基盤を使っているのなら「このサービス以外のサービスとの疎通」の定義に合致しそうですし。

とはいえユーザーとアプリケーションの一番最初の接点(UI)でユーザーの認証を行なっておく・・・というのも不自然な配置ではないように感じますし、これはどちらでも良いかな?というのが僕の現時点の認識です。

認証結果のインターフェースはアプリケーション層に書くの?

認証後は、ユーザーの認証成否や必要であれば権限などをプロパティに持つ認証結果クラスのインスタンス({isLoggedIn: boolean, roles: Role[]}みたいな)をアプリケーション層に渡して、アプリケーション層の中で認証状態に応じて処理を分けるようなコードが多いと思いますが、果たしてこの認証結果クラスのインターフェースはどこに書けば良いのでしょうか?

これも消去法で考えてみます。アプリケーション層の中で認証結果に応じてユースケースを分岐させる場合、UI/Infrastructure層にインターフェースを置いてあると、依存関係の向きが内側(アプリケーション層)から外側(UI/Infrastructure層)に向かってしまうため、これは避けた方が良いのでは無いでしょうか。円の外側から内側に向けて、依存関係を1方向に制限したいので。

となると、アプリケーション層で扱うインターフェースはドメインかアプリケーションのどちらかに置く必要があります

ドメイン層に認証に関するインターフェース??

ドメイン層は現実世界にあるルールだけが存在する世界を目指すわけですから(と個人的に認識している)、自身がCLIで実行されるコマンドなのかWEBアプリケーションなのか、全く知らない状態がベストです。WEBアプリケーション固有の情報をドメイン層が持っているということは、WEBアプリケーションではなくなった時、ドメイン層の変更が必要になるわけですから。

そう考えると認証に関するインターフェースがドメイン層にあるのは違和感があるので、消去法で考えていくとインターフェースはアプリケーション層に置くのが良さそうだと思うのですが、他の方の意見も聞いてみたいです

とはいえレポジトリ層(実装はインフラ層)のインターフェースはドメイン層に置かれます。実現方法は知らないけど何らかの永続化の手段があることまではドメイン層が認識しているので、実現方法は知らないけど何らかの認証手段があることまでドメイン層が知っているのは許されても構わない気がします。

外側から層を跨いで内側を参照することを許可する/しないという大きな考えの分岐もあるので、より厳しい方針を採用するのに備えるのであれば、層を跨がずアプリケーション層にインターフェースを置くのが素直かもしれません。どちらにせよ依存性の向きは外側(Infrastructure)から内側に向くので

蛇足:認証をアプリケーション層の外側に実装すると、アプリケーション層の単体テストを書きやすくなる?

少し話が逸れますが、アプリケーション層が「認証結果クラスのインターフェースを持つ何か」を引数として受け取り、この引数によりアプリケーション層の処理が分岐するようになると、単体テストが書きやすくなる利点があります。

{isLoggedin: false}を引数に渡せば認証失敗時のユースケースを実行したり、{roles: [Admin]}を引数に渡せばアドミに権限を持っている時の挙動を、{roles: []}を引数に渡せば必要な権限を何も持っていない時の挙動を簡単にテストできます。

もし認証の実装をアプリケーション層の中に書いてしまうとこういうテストは書きづらくなってしまうのも、認証をアプリケーション層の1つ外に書いておくメリットではないでしょうか。認証処理の詳細が書かれたクラスをアプリケーション層のクラスにDIするような設計であれば、認証処理クラスそのものをモックして単体テスト可能ですが、クラス全体をモックするよりは結果をモックする方が簡単だと思うので・・・

蛇足:Userエンティティの落とし穴?

少し話がそれますが「User」モデルに「mailAddress」とか「loginToken」みたいな認証関連のプロパティが含まれているのを見かけます。本当にドメインロジックの一環でメルアドやトークンが使われるのなら良いのですが、多分そうではなく アクターとしてのUserとドメインエンティティとしてのUserが混在している ケースが多い気がします。

ブログサービスであれば「閲覧者」と「記事の執筆者」とか、ECサイトであれば「出店者」と「購入者」とか、本来はUserよりもっと細かいドメイン・エンティティとして表現されるべきものがたまたまUserとしか定義されていなくて、そこにアクターとしての情報まで含まれてしまうと、Userが多くの責務を持ちすぎている(ドメインロジックと認証ロジックの両方を持つ)のかなぁ・・・と思ったり

PrAha

Discussion