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


8

したがって、「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. アプリケーション層サービスには別のインターフェースが必要ですか?答えが「はい」の場合、それらはどこに配置する必要がありますか?


2
これらは非常に多様な問題です。これを別々の質問に分割することをお勧めします。
guillaume31 2016年

回答:


7

ドメインサービスは、インフラストラクチャレイヤーとモデル間の抽象化レイヤーを提供するために存在する単なるインターフェイスですか?例:リポジトリ+ 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用語のモジュールという言葉は、あなたが説明しているものとは異なる意味を持っています。モジュールは、関連する概念を収容する必要があります。たとえば、ドメインレイヤーの注文モジュールには、Order集計、OrderItemエンティティ、OrderRepositoryInterface、MaxOrderValidationServiceを格納できます。

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

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

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

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

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

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


あなたが書いたほとんどのことには同意しますが、フレームワークのRADの部分はアプリケーションのプロトタイプを作成するのに非常に役立つと言いたいのです。ボブおじさんは別の種類のDTOであり、アダプターはドメインレイヤーとデータソースコマンドを仲介することができるため、DTOと見なすことができるので、データソースレイヤーとしてアクティブなレコード実装を持っています。ベストプラクティスは、 Forms to Command(DTO)はエンティティではなく、ほとんどすべてのフレームワークにDIコンテナがあるため、インターフェイスを使用します。
Cherif BOUCHELAGHEM

1

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

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

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

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

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

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

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

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

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

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

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

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


統合は、各モジュールのアプリケーションパッケージ/レイヤーの問題です。つまり、サービスコンテナの動的バインディング+ルート。モジュール間の関係について-別の個別のモジュール(ProjectManagementなど)が独自のAPI内で何らかのタイプの認証を実行する必要がある場合、「アクセス」モジュールのアプリケーション層をリストされた依存関係として含めます。次に、パッケージマネージャーは残りのネストされた依存関係を取得します。アプリケーションにアクセス->インフラにアクセス->ドメインにアクセスして、必要な作業を実行できるようにします。
user2308097 2016年

1
はい、そのようなクロスドメインリンクはおそらく間違いだと思います。循環依存が発生する可能性があります
Ewan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.