web-dev-qa-db-ja.com

DDDアプリケーションサービスとREST APIの間の概念的な不一致

複雑なビジネスドメインとREST API(厳密にはRESTではなく、リソース指向)をサポートするための要件)を備えたアプリケーションを設計しようとしています。リソース指向の方法でドメインモデルを公開する方法。

DDDでは、ドメインモデルのクライアントは、エンティティとドメインサービスによって実装されるビジネス機能にアクセスするために、手続き型の「アプリケーションサービス」層を通過する必要があります。たとえば、Userエンティティを更新する2つのメソッドを持つアプリケーションサービスがあります。

userService.ChangeName(name);
userService.ChangeEmail(email);

このアプリケーションサービスのAPIは、状態ではなくコマンド(動詞、プロシージャ)を公開します。

しかし、同じアプリケーションにRESTful APIも提供する必要がある場合は、次のようなUserリソースモデルがあります。

{
name:"name",
email:"[email protected]"
}

リソース指向のAPIは、コマンドではなく状態を公開します。これにより、以下の懸念が生じます。

  • REST APIに対する各更新操作は、リソースモデルで更新されているプロパティに応じて、1つ以上のアプリケーションサービスプロシージャコールにマップできます。

  • 各更新操作はREST APIクライアントに対してアトミックのように見えますが、そのように実装されていません。各アプリケーションサービス呼び出しは個別のトランザクションとして設計されています。リソースモデルの1つのフィールドを更新すると、検証ルールが変更される可能性があります他のフィールドの場合。すべてのリソースモデルフィールドを一緒に検証して、潜在的なすべてのアプリケーションサービス呼び出しが有効になることを確認してから、それらを作成する必要があります。コマンドセットを一度に検証することは、一度に1つずつ実行するよりも簡単です。方法個々のコマンドが存在することさえ知らないクライアントでそれを行いますか?

  • REST APIは、1つのリソース内で違いがないように見えるが、アプリケーションサービスのメソッドを異なる順序で呼び出すと、異なる効果が得られる可能性がある。

私はもっ​​と似たような問題を思いつくかもしれませんが、基本的にはそれらはすべて同じことによって引き起こされています。アプリケーションサービスを呼び出すたびに、システムの状態が変化します。有効な変更のルール、エンティティが次の変更を実行できる一連のアクション。リソース指向のAPIは、すべてをアトミック操作のように見せようとします。しかし、このギャップを越えることの複雑さはどこかに行く必要があり、それは巨大なようです。

さらに、UIがよりコマンド指向である場合(多くの場合そうです)は、クライアント側でコマンドとリソースをマッピングし、次にAPI側にマッピングする必要があります。

質問:

  1. このすべての複雑さは、(厚い)RESTからAppServiceへのマッピングレイヤーで処理する必要がありますか。
  2. または、DDD/RESTの理解に欠けているものはありますか?
  3. RESTは、ドメインモデルの機能を特定の(かなり低い)程度の複雑さで公開するために単に実用的ではないでしょうか?
22
astreltsov

私は同じ問題を抱えており、RESTリソースを異なる方法でモデル化することでそれを「解決」しました。例:

/users/1  (contains basic user attributes) 
/users/1/email 
/users/1/activation 
/users/1/address

したがって、私は基本的に、大きくて複雑なリソースをいくつかの小さなリソースに分割しました。これらのそれぞれには、一緒に処理されることが予想される元のリソースの属性のややまとまりのあるグループが含まれています。

これらのリソースでの各操作はアトミックですが、いくつかのサービスメソッドを使用して実装される場合があります-少なくともSpring/Java EEでは、独自のトランザクションを意図していたいくつかのメソッドから(REQUIREDトランザクションを使用して)より大きなトランザクションを作成することは問題ではありません伝搬)。多くの場合、この特別なリソースに対して追加の検証を行う必要がありますが、属性はまとまりがある(と思われる)ため、それでも十分に管理できます。

これはHATEOASのアプローチにも適しています。より細かいリソースは、リソースで簡単に表現できないため、クライアントとサーバーの両方でこのロジックを使用する代わりに、リソースで実行できることについてより多くの情報を伝達するためです。

もちろん、完璧ではありません。UIがこれらのリソースを考慮してモデル化されていない場合(特にデータ指向のUI)、問題が発生する可能性があります。 UIは、特定のリソース(およびそのサブリソース)のすべての属性の大きな形式を提示し、それらをすべて編集して一度に保存できるようにします。これにより、クライアントがいくつかのリソース操作(それ自体がアトミックですがシーケンス全体)を呼び出す必要がある場合でも、アトミック性の錯覚が生じますアトミックではありません)。

また、このリソースの分割は、簡単ではない場合や明白でない場合もあります。私は主に、その複雑さを管理するために複雑な動作/ライフサイクルを持つリソースでこれを行います。

10
qbd

リソース指向の方法でドメインモデルを公開する方法を思い付くのに問題があります。

リソース指向の方法でドメインモデルを公開するべきではありません。リソース指向の方法でアプリケーションを公開する必要があります。

uIがよりコマンド指向である場合、これはよくあることですが、クライアント側でコマンドとリソースをマッピングし、次にAPI側にマッピングする必要があります。

まったく使用しない-ドメインモデルとインターフェースするアプリケーションリソースにコマンドを送信します。

REST APIに対する各更新操作は、リソースモデルで更新されているプロパティに応じて、1つ以上のアプリケーションサービスプロシージャコールにマップできます。

はい。ただし、これを綴る方法が少し異なるため、物事が簡単になる場合があります。 REST apiに対する各更新操作は、1つ以上の集合体にコマンドをディスパッチするプロセスにマップされます。

各更新操作はREST APIクライアントに対してアトミックのように見えますが、そのように実装されていません。各アプリケーションサービス呼び出しは個別のトランザクションとして設計されています。リソースモデルの1つのフィールドを更新すると、検証ルールが変更される可能性があります他のフィールドの場合。すべてのリソースモデルフィールドを一緒に検証して、潜在的なすべてのアプリケーションサービス呼び出しが有効になることを確認してから、それらを作成する必要があります。コマンドセットを一度に検証することは、一度に1つずつ実行するよりも簡単です。方法個々のコマンドが存在することさえ知らないクライアントでそれを行いますか?

あなたはここで間違った尾を追いかけています。

想像してみてくださいREST画像から完全に外します。代わりに、このアプリケーション用のデスクトップインターフェイスを作成していると想像してください。さらに、非常に優れた設計要件があり、タスクベースのUIを実装しているとします。 。したがって、ユーザーは、作業中のタスクに完全に合わせて調整された最小限のインターフェースを取得します。ユーザーは、いくつかの入力を指定してから、「VERB!」ボタンを押します。

今、何が起きた?ユーザーの観点から見ると、これは実行する必要のある単一のアトミックタスクです。 domainModelの観点から見ると、集約によって実行される多数のコマンドであり、各コマンドは個別のトランザクションで実行されます。それらは完全に互換性がありません!ギャップを埋めるために真ん中に何かが必要です!

何かが「アプリ」です。

ハッピーパス上で、アプリケーションはDTOを受け取り、そのオブジェクトを解析して、理解できるメッセージを取得し、メッセージ内のデータを使用して、1つ以上の集約用の整形式のコマンドを作成します。アプリケーションは、アグリゲートにディスパッチする各コマンドが適切に形成されていることを確認し(それが機能する腐敗防止レイヤーです)、アグリゲートをロードし、トランザクションが正常に完了した場合にアグリゲートを保存します。アグリゲートは、現在の状態を考慮して、コマンドが有効かどうかを判断します。

考えられる結果-コマンドはすべて正常に実行されます-汚職防止レイヤーがメッセージを拒否します-一部のコマンドは正常に実行されますが、集計の1つが不平を言い、軽減する必要があります。

次に、そのアプリケーションを作成したとします。 RESTfulな方法でどのように操作しますか?

  1. クライアントは、ハイパーメディアコントロールを含む、現在の状態(つまり、タスクベースのUI)のハイパーメディア記述から始まります。
  2. クライアントは、タスクの表現(つまり、DTO)をリソースにディスパッチします。
  3. リソースは、着信HTTP要求を解析し、表現を取得して、アプリケーションに渡します。
  4. アプリケーションがタスクを実行します。リソースの観点から見ると、これは次のいずれかの結果になるブラックボックスです。
    • アプリケーションはすべてのアグリゲートを正常に更新しました:リソースはクライアントに成功を報告し、新しいアプリケーションの状態に誘導します
    • 腐敗防止層がメッセージを拒否します。リソースはクライアントに4xxエラーを報告します(おそらく不正な要求)。発生した問題の説明を伝える可能性があります。
    • アプリケーションはいくつかの集合体を更新します。リソースは、コマンドが受け入れられたことをクライアントに報告し、コマンドの進行状況を表すリソースにクライアントを誘導します。

Acceptedは、アプリケーションがクライアントへの応答後までメッセージの処理を延期する通常のcop-outです。通常、非同期コマンドを受け入れるときに使用されます。しかし、アトミックであるはずの操作に緩和が必要な場合にも、この方法はうまく機能します。

このイディオムでは、リソースはタスク自体を表します。適切な表現をタスクリソースにポストすることにより、タスクの新しいインスタンスを開始します。そのリソースはアプリケーションとインターフェースし、次のアプリケーション状態に移動します。

ddd では、複数のコマンドを調整しているときはいつでも、プロセス(別名ビジネスプロセス、別名サガ)の観点から考える必要があります。

読み取りモデルにも同様の概念的な不一致があります。繰り返しになりますが、タスクベースのインターフェースを検討してください。タスクで複数の集約を変更する必要がある場合、タスクを準備するためのUIには、多くの集約からのデータが含まれている可能性があります。リソーススキームが集約で1:1の場合、それを調整することは困難です。代わりに、前述のように「タスクの開始」関係をタスクのエンドポイントにマップするハイパーメディアコントロールとともに、いくつかの集約からのデータの表現を返すリソースを提供します。

参照: REST in Practice by Jim Webber。

0
VoiceOfUnreason

ここでの重要な問題は、REST呼び出しが行われたときにビジネスロジックが透過的に呼び出される方法ですか?これは、RESTによって直接対処されない問題です。

JPAなどの永続化プロバイダー上に独自のデータ管理レイヤーを作成することで、これを解決しました。カスタムアノテーションを含むメタモデルを使用して、エンティティの状態が変化したときに適切なビジネスロジックを呼び出すことができます。これにより、エンティティの状態の変化に関係なく、ビジネスロジックが呼び出されます。アーキテクチャDRY=およびビジネスロジックを1か所に保持します。

上記の例を使用すると、RESTを使用して名前フィールドが変更されたときに、validateNameというビジネスロジックメソッドを呼び出すことができます。

class User { 
      String name;
      String email;

      /**
       * This method will be transparently invoked when the value of name is changed
       * by REST.
       * The XorUpdate annotation becomes effective for PUT/POST actions
       */
      @XorPostChange
      public void validateName() {
        if(name == null) {
          throw new IllegalStateException("Name cannot be set as null");
        }
      }
    }

このようなツールを自由に使用できれば、ビジネスロジックメソッドに適切に注釈を付けるだけで済みます。

0
codedabbler