アプリケーションまたはドメインサービスのDDDリポジトリ


29

私は最近DDDを勉強していますが、DDDでリポジトリを管理する方法について質問があります。

実際、私は2つの可能性に出会いました:

最初の1つ

私が読んだサービスを管理する最初の方法は、アプリケーションサービスにリポジトリとドメインモデルを挿入することです。

このように、アプリケーションサービスメソッドの1つで、ドメインサービスメソッドを呼び出し(ビジネスルールをチェック)、条件が良好な場合、データベースからエンティティを永続化/取得するために、リポジトリが特別なメソッドで呼び出されます。

これを行う簡単な方法は次のとおりです。

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repository = repository
  }

  postAction(data){
    if(this.domainService.validateRules(data)){
      this.repository.persist(new Entity(data.name, data.surname))
    }
    // ...
  }

}

二つ目

2番目の可能性は、代わりにdomainService内にリポジトリを挿入し、ドメインサービスを介してのみリポジトリを使用することです。

class ApplicationService{

  constructor(domainService){
    this.domainService = domainService
  }

  postAction(data){
    if(this.domainService.persist(data)){
      console.log('all is good')
    }
    // ...
  }

}

class DomainService{

  constructor(repository){
    this.repository = repository
  }

  persist(data){
    if(this.validateRules(data)){
      this.repository.save(new Entity(data.name))
    }
  }

  validateRules(data){
    // returns a rule matching
  }

}

今から、どちらがベストか(ベストがあれば)、またはそれらが文脈の両方で暗示しているものを区別することはできません。

片方がもう片方よりも優れている可能性がある例とその理由を教えてください。



「アプリケーションサービスにリポジトリとドメインモデルを注入する。」どこかに「ドメインモデル」を挿入するとはどういう意味ですか?DDDドメインモデルの観点からAFAICTは、ドメインからの概念全体と、アプリケーションに関連する概念間の相互作用を意味します。これは抽象的なものであり、メモリ内のオブジェクトではありません。注入することはできません。
アレクセイ

回答:


31

簡単な答えは-アプリケーションサービスまたはドメインサービスのリポジトリを使用できますが、その理由と方法を検討することが重要です。

ドメインサービスの目的

ドメインサービスは、ドメインの概念/ロジックをカプセル化する必要があります。そのため、ドメインサービスメソッドは次のとおりです。

domainService.persist(data)

ユビキタス言語の一部ではなく、永続性の操作はドメインのビジネスロジックのpersist一部ではないため、ドメインサービスに属していません。

一般に、ドメインサービスは、複数のアグリゲートを調整または操作する必要があるビジネスルール/ロジックがある場合に役立ちます。ロジックが1つの集計のみを含む場合、その集計のエンティティのメソッドにある必要があります。

アプリケーションサービスのリポジトリ

したがって、その意味で、あなたの例では、私はあなたの最初のオプションを好みます-しかし、ドメインサービスがAPIから生データを受け入れるため、改善の余地があります-ドメインサービスはなぜ構造を知っているべきdataですか?さらに、データは単一の集計にのみ関連しているように見えるため、そのためにドメインサービスを使用することの価値は限られています。通常、検証はエンティティコンストラクター内に配置します。例えば

postAction(data){

  Entity entity = new Entity(data.name, data.surname);

  this.repository.persist(entity);

  // ...
}

無効な場合は例外をスローします。アプリケーションフレームワークによっては、例外をキャッチし、APIタイプの適切な応答にマッピングするための一貫したメカニズムを持つことは簡単かもしれません。たとえば、REST APIの場合、400ステータスコードを返します。

ドメインサービスのリポジトリ

上記にも関わらず、ドメインサービスにリポジトリを挿入して使用すると便利な場合がありますが、リポジトリが集約ルートのみを受け入れて返すように実装されている場合や、複数の集約を含むロジックを抽象化する場合に限ります。例えば

postAction(data){

  this.domainService.doSomeBusinessProcess(data.name, data.surname, data.otherAggregateId);

  // ...
}

ドメインサービスの実装は次のようになります。

doSomeBusinessProcess(name, surname, otherAggregateId) {

  OtherEntity otherEntity = this.otherEntityRepository.get(otherAggregateId);

  Entity entity = this.entityFactory.create(name, surname);

  int calculationResult = this.someCalculationMethod(entity, otherEntity);

  entity.applyCalculationResultWithBusinessMeaningfulName(calculationResult);

  this.entityRepository.add(entity);

}

結論

ここで重要なのは、ドメインサービスがユビキタス言語の一部であるプロセスをカプセル化することです。その役割を果たすためには、リポジトリを使用する必要があります-そして、それを行うことはまったく問題ありません。

しかし、リポジトリをラップするドメインサービスを追加するというメソッドはpersist、ほとんど価値がありません。

それに基づいて、アプリケーションサービスが単一のアグリゲートでのみ作業することを要求するユースケースを表現している場合、アプリケーションサービスから直接リポジトリを使用しても問題はありません。


さて、ビジネスルール(仕様パターンルールを認める)がある場合、それが1つのエンティティのみに関係するのであれば、そのエンティティの検証以外の必要はありませんか?ユーザーエンティティ内で適切なユーザーメール形式を制御するなどのビジネスルールを導入するのは奇妙に思えます。じゃない?グローバルな対応について、ありがとうございます。「適用するデフォルトのルール」はないことがわかり、それは実際にユースケースに依存しています。私はこの仕事のすべてをうまく区別するためにやるべき仕事があります
-mfrachet

2
明確にするために、エンティティに属するルールは、そのエンティティの責任であるルールのみです。良いユーザーのメール形式を制御することは、ユーザーエンティティに属しているとは感じません。個人的には、そのような検証ルールを電子メールアドレスを表す値オブジェクトに入れたいと思っています。ユーザーはEmailAddress型のプロパティを持ち、EmailAddressコンストラクターは文字列を受け入れ、その文字列が必要な形式と一致しない場合に例外をスローします。その後、電子メールアドレスを保存する必要がある他のエンティティでEmailAddress ValueObjectを再利用できます。
クリスサイモン

さて、Value Objectを使用する理由がわかりました。しかし、それは値オブジェクトがフォーマットを管理するビジネスルールであるプロパティを負っていることを意味しますか?
mfrachet 16

1
値オブジェクトは不変でなければなりません。通常、これはコンストラクターで初期化および検証し、すべてのプロパティでパブリックget / private setパターンを使用することを意味します。ただし、言語構造を使用して、平等、ToStringプロセスなどを定義できます。例:kacper.gunia.me/ddd-building-blocks-in-php-value-objectまたはgithub.com/spring-projects/spring-gemfire-examples/ blob / master /…
クリスサイモン

最後に、@ ChrisSimonに感謝します。そして、理論だけでなくコードを含む実際のDDD状況に答えてください。集計の作成と保存の機能的な例のためにSOとWebを5日間トロールしましたが、これは私が見つけた最も明確な説明です。
e_i_pi

2

受け入れられた回答に問題があります:

ドメインモデルはリポジトリに依存できません。ドメインサービスはドメインモデルの一部です-> ドメインサービスはリポジトリに依存しないでください。

代わりに行うべきことは、アプリケーションサービスで既に実行されているビジネスロジックの実行に必要なすべてのエンティティをアセンブルし、インスタンス化されたオブジェクトをモデルに提供することです。

例に基づいて、次のようになります。

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repositoryA = repositoryA
    this.repositoryB = repositoryB
    this.repositoryC = repositoryC
  }

  // any parsing and/or pre-business validation already happened in controller or whoever is a caller
  executeUserStory(data){
    const entityA = this.repositoryA.get(data.criterionForEntityA)
    const entityB = this.repositoryB.get(data.criterionForEntityB)

    if(this.domainService.validateSomeBusinessRules(entityA, entityB)){
      this.repositoryC.persist(new EntityC(entityA.name, entityB.surname))
    }
    // ...
  }
}

したがって、経験則:ドメインモデルは外層に依存しません

アプリケーションとドメインサービスこの記事 から:

  • ドメインサービスは非常にきめ細かく、アプリケーションサービスはAPIの提供を目的としたファサードです。

  • ドメインサービスには、エンティティまたは値オブジェクトに自然に配置できないドメインロジックが含まれていますが、アプリケーションサービスはドメインロジックの実行を調整し、それ自体はドメインロジックを実装しません。

  • ドメインサービスメソッドは、他のドメイン要素をオペランドおよび戻り値として持つことができますが、アプリケーションサービスは、アイデンティティ値やプリミティブデータ構造などの単純なオペランドを操作します。

  • アプリケーションサービスは、ドメインロジックの実行に必要なインフラストラクチャサービスへの依存関係を宣言します。


1

サービスとオブジェクトが一貫した一連の責任をカプセル化しない限り、どちらのパターンも適切ではありません。

まず、ドメインオブジェクトとは何かを言い、ドメイン言語内で何ができるかについて話します。有効または無効にできる場合、ドメインオブジェクト自体のプロパティとしてこれを持たないのはなぜですか?

たとえば、オブジェクトの有効性が別のオブジェクトに関してのみ意味がある場合は、サービスのセットにカプセル化できる「ドメインオブジェクトの検証ルールX」を担当している可能性があります。

オブジェクトを検証するには、ビジネスルール内にオブジェクトを保存する必要がありますか?おそらくない。「オブジェクトの保存」の責任は通常、別個のリポジトリオブジェクトにあります。

これで、さまざまな責任をカバーし、オブジェクトを作成し、検証し、有効な場合は保存する、実行する操作ができました。

この操作はドメインオブジェクトに固有ですか?次に、それをそのオブジェクトの一部にします。すなわちExamQuestion.Answer(string answer)

ドメインの他の部分に適合しますか?そこにそれを置きますBasket.Purchase(Order order)

ADM RESTサービスを実行しますか?じゃあ

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.