【DDD】ドメイン層、アプリケーション層、それぞれの大まかな特徴
はじめに
リポジトリの実装部分はインフラ層に置くべきなのはわかるが、
そのインターフェースはアプリケーション(ユースケース)層かドメイン層か、どっちに置くべきなのか、という議論をよくお見受けします。
私は、インターフェースはドメイン層に置くべきと考えます。
その根拠となる考え方として、ドメイン層と、アプリケーション層との簡単な区別方法を含め、
両層の大まかな特徴を述べさせていただきます。
インターフェース ⊂ ドメイン層 であるべき根拠
ドメイン層と、アプリケーション層との区別方法とは?
ドメイン層とその外にあるアプリケーション層との違い、その区別方法ですが、
それは端的に言って、
ビジネスロジック(ドメイン知識)に含まれるか否か、
もっと簡単にいうと、
仮に作りたいサービスをアナログ化させた場合に消え去るものなのか否か、
で判別できると考えています。
ドメイン層とは
業務知識やロジックをモデル化したオブジェクトやメソッドを含む層であり、
アプリケーション層とは
業務知識やロジックの要素ではなく、ソフトウェア特有の動作や役割を担うオブジェクトやメソッドを含む層であります。
そこに明確な違いが存在しています。
では、そのビジネスロジック(ドメイン知識) とはいったいなんなのでしょうか?
ビジネスロジック(ドメイン知識)とは?
以下の図をご覧ください。
この図はビジネスロジックすなわちドメイン知識をモデル図化した、ドメインモデル図です。
この図の場合では、単に「各ユーザがメモを作成するアプリ」のドメイン知識(業務知識)をモデル図化したものです。
図では、認証ファサード(= ユーザリポジトリ)からユーザエンティティを取り出し、そのユーザが、さらにメモリポジトリから取り出されたメモを持つという、至って簡素な仕組みです。
では、この場合実現されるサービスが、
もしソフトウェアなど使われず全てアナログの物質などで実現されていたら?
を考えてみてください。
すると、
実際のメモ(例えば付箋、メモ帳など)を実際の人間が持っていたり扱っている
イメージとなるはずです。そこにソフトウェアは存在しません
(後述しますが、ソフトウェアの有無で層を判別することは間違いです)。
つまり、その現実イメージでも存在するもの(使用する人間、付箋やその作成、編集など)
がすなわち、ビジネスロジック(ドメイン知識)に含まれるというわけです。
リポジトリインターフェースのイメージ
ここで、図のメモリポジトリについて考えましょう。
ご周知の方もおられるかと思いますが、
リポジトリとは英語で「Repository (貯蔵庫)」を意味します。
そのため、仮にこれがアナログの場合でも、
実際の付箋やメモ帳などは引き出しの中や机の上など、どこかに置かれているか保存されているはずです。
すなわち、この置き場がモデル化されたものがリポジトリインターフェースなわけです。
もしリポジトリインターフェースがアプリケーション層にあるなら、メモ置き場はソフトウェア特有のもので現実には存在しない、という不可解な矛盾が生じます。
つまりリポジトリはれっきとしたドメイン知識、ドメイン層に置かれるべきものだというわけです。
そして、インターフェースとは実際に使用者が接触する部分ですが、リポジトリインターフェース(貯蔵庫)は「まさか自分がソフトウェア(データベースなど)を用いて実装されるなど、知る由もない」というようなイメージです。
とにかくシステムをアナログ化してみることで、何がドメイン知識に含まれるのかに関する理解にとても良くつながります。
実際、DDDの理論の中で
「ソフトウェアを構築できるエンジニアと、ソフトウェアやITのことは正直さっぱりでも特定の業務知識に関する知識は豊富なドメインエキスパートがいて、双方の意思疎通によってシステムを構築していくことが大事だ」
といった旨が言われています。
ドメインエキスパートが持っているのがドメイン知識であり、
彼らがたとえ全くソフトウェア関係に造詣がなくとも成り立っているものなのです。
それがエンジニアによってモデル化されコード化されたものがドメイン層に含まれるわけです。
アプリケーション層には何が入る?
対して、アプリケーション層には
ソフトウェア特有のオブジェクトやメソッドも含めた「ユースケース」が置かれます。
先ほどのメモアプリの例でまた考えてみます。
ここで、一旦ドメイン層の話になりますが、
「メモを作成する」「編集する」といったメソッドはメモエンティティ”外”の存在が行え
これはすなわちユーザエンティティが行うことなので、
これはユーザエンティティの持つメソッドとして表現されるべきです。
話は戻って、アプリケーション層には
実際のユーザが行う処理を、ソフトウェア特有の処理も含めて改めて「ユースケース」として構築します。
この例の場合では、「メモを作成する」という「ユースケース」を、
ドメインサービスを用いて(この場合はソフトウェア特有のオブジェクトやメソッドは見当たらない)
「NoteService」などのクラス内に「createNote()」メソッドとして構築したもの
がアプリケーション層に置かれます。
class NoteService {
// ユーザエンティティを引数として受け取る
final User _user;
NoteService(this._user);
void createNote() {
// メモを作成する処理
_user.createNote();
}
}
メールアドレス、パスワードもアプリケーション層に!
上記に関連して、
ユーザ認証にしか使われない「メールアドレス」「パスワード」などの値オブジェクトは、
アプリケーション層に置くべきだと考えられます。
その理由も単純で、ドメイン知識に含まれないからです。
仮に認証方法を、メアド認証をやめて、Googleなどのアカウント認証だけにしたいと切り替えただけで、ドメイン知識までにもその変更が及んでしまうわけにはいきません。
ドメイン層内のコードが変更されるのは、ドメイン知識そのものが変更されたときに限ります。
もし頭ごなしに「エンティティと値オブジェクトはドメイン層だ!」と思われる方がいらっしゃれば、それは巷でいう「軽量DDDに陥っている」状態だと考えられます。
概念的な仕組みを考えず形式だけを取り入れ、間違いに陥ってしまっている状態であります。
さらに…
インターフェース ⊂ ドメインサービス
また、リポジトリは集約のルートエンティティや値オブジェクト”外”のオブジェクトであるため、
ドメインサービスに置かれることが適切だとも考えられます。
ビジネスロジックそのものにソフトウェアが含まれる場合
「じゃあ、そもそもソフトウェアありきのサービスを作りたい場合、どうするの?
アナログ化なんてそもそも不可能だ」と思われる方もいらっしゃるでしょう。
上記の例ではわかりやすく”ソフトウェアなしで成り立つサービス”の場合を考えましたが、例えば「メールサービス」「ゲームサービス」など、どうしても現実の物質としては具現化できないものを扱う場合です。
ですがその場合も単純で、
ドメイン知識に含まれないソフトウェア部分はアプリケーション、ドメイン知識に含まれるソフトウェア部分はドメイン層、
すなわちここでもドメイン層の内外という単純な一つの指標によって判断が可能です。
おわりに
未だ実務経験皆無で駆け出し勉強中かつメンターもいない身ですが、本記事が参考になりましたら幸いです。
初心者ゆえ、ご指摘、アドバイス等ございましたら、是非ともよろしくお願いいたします。
参考文献
オニオンアーキテクチャにておいて、ドメイン層とアプリケーション層の責務はどう違うのか[DDD]
ドメイン知識とユースケースの違いは何か?[ドメイン駆動設計][DDD]
メールのドメインモデリング
良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方
Discussion