はじめに
過去2回のパッケージ構成について
約3年ぶりに改めて考察してみようかと。
DDD関連に影響は受けていると思いますが別物です。
3層+ドメイン を基本とした構成です。
なぜ改めて見直すのか?
Reactなどの開発や参考になる情報も増えてきたり、サーバーサイドについてもWebAPIを作成したり、Spring関連*1の勉強もしたりするの中で、以前の整理では ちょっと良くないなと思うところが自分で読み返しても感じたため。
特に WebAPI(RESTful API)については全く考えていませんでした。
パッケージ構成図
ブラウザで見るには小さいのでリンクからsvgファイルを取得してください。
https://raw.githubusercontent.com/vermeer-1977-blog/blog-parts/main/package-%20examination/img/package.drawio.svg
シーケンス
所感的な補足
baseとapps
分類として横断的機能と業務遂行の中心となるパッケージを分けました。
非機能要件と機能要件を分けるというのも類似です。
技術要素によったパッケージとして core
でも良いのですが中心というイメージではないかな、と。
また意図としてはshare
も近い気がしますが、そういう横断的ではなくて箱庭の枠みたいなイメージで同列性を感じない表現が近しいイメージです。
複数のサービスを参照するものの分類は設けない。
複数のサービスの参照じゃなくなったときに命名やパッケージを変更するというのは、なんか違うかな、と。 そういう「複数のサービスを利用している」「別のサービスからも利用されている」というのは、コード分析(JIGみたいな)で取得して把握すれば必要十分だろうということで設けないことにしました。
RepositoryはDomain以外の場所に配置(Application層 or 独立させる)
ドメインオブジェクトから参照はしないから。
何か関数的な操作をRepositoryに「させる」ことはあっても、Repostoryがドメイン内の操作を「する」ことはない。
もし「ある」場合は、ドメインからIO(副作用)があることになりかねない。
逆に非常に簡易的な参照系システムの場合 Presentation層から直接 Repositoryを使用することもアリかなと思ってもいます(トランザクション境界が不要だから)。
となると、RepositoryはPresentation層から使用も出来るし、Application層からも使用できる場所に配置させるのが妥当ではないかな?という結論に至りました。
Service内でのif分岐はあり
構成図には記載していませんが、かつては、if分岐すらも無しに出来ないかな?と思ったことがあります。
ですが、UMLのユースケースでも 代替フローがあるので、さすがにやりすぎだと今は思っています。
Serviceの命名は異論あるかも
個人的にはサービスは動詞がアリだと思うのですが ちょっと万人向けじゃないかもなぁと思っています。
とはいえ ユースケース記述に近い実装のやりやすさを考えると、クラス名もユースケース記述に近い「動詞」が良いんじゃないかなぁと。
ということで、ここは以前の考えのままにしました。
Repositoryに重複データ検証用の問い合わせを設ける
1つの理由は上述の「RepositoryはDomain以外に配置」です。
またドメインサービスの例として重複データ検証があると思います。 重複検証がエンティティに実装する振舞いには不適当だからという文脈からいう理解をしています。 つまり「永続情報から何をもって重複判定を行うのか?」という知識を どこまでコード側(Domain)で扱うか?というところだと思います。 僕は「Repositoryのメソッド名による表明で必要十分」という整理に落ち着きました。 たとえば、Repositoryを経由して問い合わせる先がDBではなく外部APIだったらどうでしょうか? そもそもそういった問い合わせ先の具体的な詳細を隠蔽することがRepositoryを用いたDIPの目的であると考えています。
さて、そうするとドメインサービスはいらないの?という問いが出てきます。
僕としては そもそも僕自身がドメインサービスの理解を誤っていた、というのが結論です。
ドメインサービスは
- 状態を持たない関数として実装する
- 複数ドメインオブジェクトを組み合わせて表現されるルールを扱う
- 複数のドメインオブジェクトを扱うにあたって、どちらのドメインオブジェクトにロジックを配置するのか悩ましいときの解決方法(つまり、どちらにもロジックを配置しない)
こう理解をすると、IOは不要でも違和感はありません。
きっかけは 以下の指摘で理解が進んだ気がという感じです。
userRepository.containsByName(name): Booleanとかですか?ドメインサービスの仕事ではなく、アプリケーションサービスからリポジトリに聞くべきですね。ドメインサービスはドメイン上の計算を請け負うただの関数ですね。責務が違います。
— かとじゅん (@j5ik2o) 2021年2月14日
なお、僕はドメインサービスという名称はアプリケーションサービスもあって分かりにくいので「ルール(Rule)」としています。
Factoryを省略
集約ルートにメソッドを準備する、クラスとして独立させる、ということは目的と規模に応じて適宜判断するものであると考えました。
何かしら必ず作成するようなものではないので、構成図からは外すことにしました。
ライセンス
構成図は MITなので ご自由に使用してください。
Draw.ioがあれば 修正も出来ます。
なお 説明、パッケージオブジェクト、下地の3つのレイヤーにしてロックしているので、編集時には適宜 レイヤーのロックを解除してください。
参考
Atomic Designをやめてディレクトリ構造を見直した話|食べログ フロントエンドエンジニアブログ|note
さいごに
Draw.io を svg形式で保存できるのは本当に便利。
*1:Spring Bootとか Spring Securityとか