web-dev-qa-db-ja.com

DDD:再利用可能なモジュールとサービスタイプの区別(ドメイン、インフラストラクチャ、アプリケーション)の作成

したがって、「Vaughn Vernonによるドメイン駆動設計の実装」を読んだ後、コアドメインの概念と思われるものを個別のモジュールに分離することにより、再利用性を高めるためにコードをリファクタリングすることにしました。

各モジュールには、ドメイン、インフラストラクチャ、アプリケーション/プレゼンテーションレイヤーを含む独自のアーキテクチャレイヤーのセットが含まれています(ヴォーンの推奨に従って、アプリケーションレイヤーの責任をルート、MVCコントローラー+テンプレートに存在するテンプレートからさらに分離することにしましたプレゼンテーション層)。

これらの各レイヤーを独自のパッケージ内に配置することにしました。そして、各パッケージはその下のレイヤーを依存関係として参照しています。つまり、プレゼンテーション層はアプリケーション層に依存し、アプリケーションはインフラストラクチャに依存します。リポジトリはドメインの一部であるため、各リポジトリインターフェイスはドメイン層/パッケージ内に存在し、実装はインフラストラクチャ層/パッケージ(Doctrine 、など)。

この方法でコードを再構築することで、アプリケーションレイヤーをスワップアウトし、複数のWebアプリケーション間でドメインを再利用できることを願っています。

最終的にコードは再び形を整え始めているように見えますが、それでも私を混乱させるのは、アプリケーション、インフラストラクチャ、ドメインサービスのこの違いです。

ドメインサービスの一般的な例の1つは、パスワードのハッシュに使用するものです。ユーザーエンティティは、ユーザーの資格情報を格納するために使用される可能性のあるさまざまなハッシュアルゴリズムに関与する必要がないため、これはSRPの観点からは理にかなっています。

それを念頭に置いて、私はこの新しいドメインサービスを私のリポジトリと同じように扱いました。ドメインでインターフェースを定義し、実装をインフラストラクチャ層に任せることにより。しかし、私は今、アプリケーションサービスで何をすべきかについて考えています。

現在のところ、各エンティティは独自のアプリケーションサービスを持っています。つまり、ユーザーエンティティはアプリケーションレイヤー内にUserServiceを持っています。この場合のUserServiceは、プリミティブデータ型の解析と一般的なユースケース「UserService :: CreateUser(string name、string email、etc):User」の処理を担当します。

私が気になるのは、アプリケーション層を交換することにした場合、複数のアプリケーションにわたってこのロジックを再実装する必要があるという事実です。だから私はこれが私の次のいくつかの質問につながると思います:

  1. ドメインサービスは、インフラストラクチャレイヤーとモデル間の抽象化レイヤーを提供するために存在する単なるインターフェイスですか?例:リポジトリ+ HashingServicesなど.

  2. 私は次のようなアプリケーションサービスがあることを述べました。

    • Access/Application/Services/UserService :: CreateUser(string name、string email、etc):User

    • メソッドシグネチャは、プリミティブデータ型の引数を受け入れ、新しいユーザーエンティティ(DTOではない!)を返します。

    これは、ドメインレイヤー内で定義されたインターフェイスの実装としてインフラストラクチャレイヤーに属しますかまたは、アプリケーションレイヤーは、プリミティブデータ型の引数などにより、実際にはより適切です

    例:

    Access/Domain/Services/UserServiceInterface 
    

    そして

    Access/Infrastructure/Services/UserService implements UserServiceInterface
    
  3. 個別のモジュールが一方向の関係をどのように処理するか。モジュールAは、モジュールBのアプリケーションレイヤー(現在行っているように)またはインフラストラクチャの実装(個別のインターフェイスを介して)を参照する必要がありますか?

  4. アプリケーション層サービスには別のインターフェースが必要ですか?答えが「はい」の場合、それらはどこに配置する必要がありますか?

8
user2308097

ドメインサービスは、インフラストラクチャレイヤーとモデル間の抽象化レイヤーを提供するために存在する単なるインターフェイスですか?例:リポジトリ+ HashingServicesなど.

ドメインサービスの責任には、いくつかのものが含まれます。最も明白なのは、単一のエンティティに適合しないハウジングロジックです。たとえば、特定の購入の払い戻しを承認する必要がある場合がありますが、操作を完了するには、Purchaseエンティティ、Customerエンティティ、CustomerMembershipエンティティからのデータが必要です。

ドメインサービスは、ドメインがPasswordEncryptionServiceなどの機能を完了するために必要な操作も提供しますが、このサービスの実装は、主に技術的なソリューションであるため、インフラストラクチャレイヤーに存在します。

インフラストラクチャサービスは、ネットワーク接続を開く、ファイルシステムからファイルをコピーする、外部Webサービスと通信する、データベースと通信するなどのインフラストラクチャ操作を行うサービスです。

アプリケーションサービスは、構築しているアプリケーションのユースケースの実装です。フライトの予約をキャンセルする場合:

  1. データベースに予約オブジェクトを照会します。
  2. reservation-> cancelを呼び出します。
  3. オブジェクトをDBに保存します。

アプリケーション層はドメインのクライアントです。ドメインはあなたのユースケースが何であるかを知りません。集約とドメインサービスを通じて機能を公開するだけです。ただし、アプリケーションレイヤーは、ドメインレイヤーとインフラストラクチャレイヤーを調整することによって達成しようとしていることを反映しています。

Access/Application/Services/UserService :: CreateUser(string name、string email、etc):Userメソッドシグネチャはプリミティブデータ型引数を受け入れ、新しいユーザーエンティティ(DTOではない)を返します!)。

PHPフレームワークの多く(Laravel、Symfony、Zendなど))はRADを促進する傾向があるため、PHPはDDDについて学ぶのに最適な場所ではない可能性があります。 CRUDおよびエンティティへのフォームの変換CRUD!= DDD

プレゼンテーション層は、リクエストオブジェクトからフォーム入力を読み取り、正しいアプリケーションサービスを呼び出す必要があります。アプリケーションサービスはユーザーを作成し、ユーザーリポジトリを呼び出して新しいユーザーを保存します。オプションで、ユーザーのDTOをプレゼンテーション層に返すことができます。

個別のモジュールが一方向の関係をどのように処理するか。モジュールAは、モジュールBのアプリケーションレイヤー(現在行っているように)とインフラストラクチャの実装(別のインターフェイスを介して)のどちらを使用すべきですか

DDD用語のWordモジュールは、あなたが説明しているものとは異なる意味を持っています。モジュールは、関連する概念を収容する必要があります。たとえば、ドメインレイヤーの注文モジュールには、Order集計、OrderItemエンティティ、OrderRepositoryInterface、およびMaxOrderValidationServiceを格納できます。

アプリケーション層のOrderモジュールには、OrderApplicationServie、CreateOrderCommand、およびOrde​​rDtoを格納できます。

レイヤーについて話している場合、各レイヤーは可能な限り他のレイヤーのインターフェイスに依存していることが望ましいです。プレゼンテーション層は、アプリケーション層のインターフェースに依存する必要があります。アプリケーション層は、リポジトリまたはドメインサービスのインターフェースを参照する必要があります。

私は個人的にエンティティと値オブジェクトのインターフェースを作成しませんcozインターフェースは動作に関連しているべきだと思いますが、YMMV :)

アプリケーション層サービスには別のインターフェースが必要ですか?答えが「はい」の場合、それらはどこに配置する必要がありますか?

それは依存します:)複雑なアプリケーションの場合、厳密なユニット、統合、受け入れテストを適用して、インターフェースを構築します。ここでは疎結合が重要であり、インターフェースは同じ層(アプリケーション層)にあります。

単純なアプリの場合、アプリサービスに対して直接ビルドします。

7
Songo

長い質問に対する短い回答ですが、パターンは次のように見えます

ルール1:ドメインオブジェクトには単一の集約ルートが必要です

ルール2:集合ルートは大きすぎてはいけません。物事を境界コンテキストに分割してください

問題:集合根がすぐに大きくなりすぎて、それらのさまざまなドメインモデル間に線を引く明確な方法がない

ソリューション:ドメインサービス。ドメインモデルに注入できるインターフェースを作成し、ドメインコンテキストまたは集約ルートの外で実行できるようにします。

だから私はあなたの例は単なる通常のサービス/リポジトリなど、IDatabaseRepositoryForStoringUsersまたはIGenericHashingCodeだと思います

ドメインサービスは、境界コンテキスト間の通信を可能にします。すなわち

User.UpgradeAccount(IAccountService accountService)
{
    accountService.UpgradeUsersAccount(this.Id);
}

ユーザーとアカウントが別々の集約ルート/制限付きコンテキストにある場合。

ユーザーとアカウントが同じ集約ルートにある場合、もちろん次のことができるはずです。

User.UpgradeAccount()
{
    this.MyAccount.Upgrade();
}

NTierアプリケーション/インフラストラクチャーとモジュールスタッフをどのように統合しているのか、あなたの質問からは完全に明確ではありません。境界コンテキスト間の相互参照が本当に必要ない場合は、ドメインサービスインターフェイスを、他の境界コンテキストを参照しない独自のモジュールに配置します。ベース値タイプ、またはおそらくDTOのみを公開するように制限する

1
Ewan