little hands' lab

ドメイン駆動設計を布教したい

オニオンアーキテクチャにておいて、ドメイン層とアプリケーション層の責務はどう違うのか[DDD]

ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か - little hands' lab
ドメイン駆動 + オニオンアーキテクチャ概略 - little hands' lab
これらの記事で書いた通り、私はDDDのレイヤードアーキテクチャを決める際にオニオンアーキテクチャの用語を使っています。これまで色々な人に説明してきて、理解につまづきやすいポイントとしてレイヤの責務の話があったので、そこについて解説します。

一発で伝わりにくい(と思っている)定義

Onion Architecture Is Interesting - DZone Java

こちらの記事で書かれている各レイヤの説明

  • Domain Layer    Domain Model layer, where our entities and classes closely related to them e.g. value objects reside    Domain Services layer, where domain-defined processes reside
  • Application Services layer, where application-specific logic i.e. our use cases reside
  • Outer layer, which keeps peripheral concerns like UI, databases or tests

和訳

  • ドメイン層
    • ドメインモデル層:エンティティやそれに関わるクラス(ValueObjectなど)を持つ
    • ドメインサービス層:ドメインが定義するプロセスを持つ
  • アプリケーション層:アプリケーション固有のロジック(ユースケースなど)を持つ
  • 外部レイヤー:UI、データベース、テストなど周辺の関心に関するロジックを持つ

これまで色々な人に説明してきたのですが、どうも上述のような説明だとしっくりきてもらえないことが多かったのです。特にアプリケーション固有のロジックという時、「アプリケーション」という単語が多義なので人によって受け取り方が異なり、いまいちしっくりこないようでした。

そこで、最近は責務の話に重点を置き、以下のような説明をするようにしています。

責務に重点を置いた定義

  • ドメイン層:データの整合性を担保するのが責務
  • アプリケーション層(ユースケース層):ドメイン層が公開するメソッドを組み合わせ、ユースケースを組み立てるのが責務
  • UI層:アプリケーションとその側の世界をつなぐのが責務

ドメイン層の責務を「データの整合性」と言うのは多少語弊があったり反論をいただくかもしれませんが、伝わりやすさを重視してあえてこう言い切ってしまっています。

以下、詳しく説明していきます。

ドメイン層

このレイヤで「データの整合性を担保する」とはどういうことでしょうか?

具体的なコード例はモデルでドメイン知識を表現するとは何かの記事を参照して頂きたいのですが、「必ず整合性を担保できるメソッドしか上位レイヤに公開しない(可視性をPublicにしない)ということを目指します。

上記記事の例で言うと、「タスクは3回だけ、1日ずつ延期することができる。」というのがデータ整合性のルールです。これを担保するためには「タスクを延期する時には延期回数を記録する」「延期前にこれまでの延期回数を確認し、3回を超えていたらエラーとする」といった制御を行う必要があります。

これを担保しないメソッドは「タスク延期回数を全く考慮せずに自由に期日を変更できる」といったものです。ActiveRecordパターンのオブジェクトのように、全てのsetterをpublic設定で公開しているのは、まさにこういう状態です。

正しく制御を行えるメソッドのみpublicにすることで、「上位レイヤがどう頑張ろうとデータの整合性を壊せない」という状態を目指します。

アプリケーション層(ユースケース層)

このレイヤの責務は「ドメイン層が公開するメソッドを組み合わせ、ユースケースを組み立てること」と書きました。

これはドメイン層の責務と何が違うのでしょうか?

別の例を出しますが、ドメイン層が「ユーザーの住所を変更するメソッド」と「固定電話番号を変更するメソッド」をそれぞれ独立したpublicメソッドとして公開していたとします。

これはドメイン層が「データの整合性的に、住所、固定電話番号をそれぞれ独立して更新することを許可している」ということを表します。

これに対してアプリケーション層では上記メソッドを組み合わせて以下のようなユースケースを実現することができます。

  • 住所だけ更新したいというユースケース
  • 固定電話番号だけ更新したいというユースケース
  • 住所、固定電話番号を同時に更新したいというユースケース

更新操作を単独で行わせたいか同時に行わせたいかは、実現したいユースケースに合わせてアプリケーション層で自由に組み合わせてよいのです。

逆に、ドメイン層が「データの整合性的に、住所と固定電話番号は同時の更新しか許可しない」としていたら、アプリケーション層もそれに従わざるを得なくなります。

(特に自社事業のwebアプリケーションにおいて、)ユースケースというのは施策に応じて短いサイクルで変化しうるものです。しかし、それに合わせてデータの整合性も同じように変化するでしょうか?答えはNoでしょう。ライフサイクルが異なるものを、それぞれのレイヤに閉じ込めることで、変化に強い設計とすることができます。

メリット

最後になぜわざわざこのようなレイヤリングを行うのか、そのメリットを書きたいと思います。

  • アプリケーション層でバグを混入させにくくなるので、バグの発生可能性を下げることができる
  • ドメイン層だけ見ればデータの整合性ルールを理解することができる
  • ライフサイクルが異なるものを、それぞれのレイヤに閉じ込めることで、変化に強い設計となる

まとめ

DDDの設計をする上で、「この処理はどこの責務か」というのは常に考える必要があります。それこそがDDDのレイヤリング設計の中心になる超重要な概念です。ぜひ設計時の参考にしてみてください。