web-dev-qa-db-ja.com

REST APIとDDD

DDD方法論を使用した私のプロジェクト。

プロジェクトには、集計(エンティティ)取引があります。この集合体には多くのユースケースがあります。

この集約のために、残りのAPIを作成する必要があります。

標準では、問題なく作成および削除できます。

1)CreateDealUseCase(名前、価格、その他多くのパラメーター);

POST /rest/{version}/deals/
{ 
   'name': 'deal123',
   'price': 1234;
   'etc': 'etc'
}

2)DeleteDealUseCase(id)

DELETE /rest/{version}/deals/{id}

しかし、残りのユースケースで何をすべきか?

  • HoldDealUseCase(id、reason);
  • UnholdDealUseCase(id);
  • CompleteDealUseCase(id、および他の多くのパラメーター);
  • CancelDealUseCase(id、amercement、reason);
  • ChangePriceUseCase(id、newPrice、reason);
  • ChangeCompletionDateUseCase(id、newDate、amercement、whyChanged);
  • など(合計20のユースケース)...

解決策は何ですか?

1)動詞を使用

PUT /rest/{version}/deals/{id}/hold
{ 
   'reason': 'test'
}

しかし!動詞はurl(in REST theory)では使用できません。

2)完了した状態を使用(ユースケースの後になります):

PUT /rest/{version}/deals/{id}/holded
{ 
   'reason': 'test'
}

個人的にはいように見えます。たぶん私は間違っています?

3)すべての操作に1つのPUTリクエストを使用:

PUT /rest/{version}/deals/{id}
{ 
   'action': 'HoldDeal',
   'params': {'reason': 'test'}
}

PUT /rest/{version}/deals/{id}
{ 
   'action': 'UnholdDeal',
   'params': {}
}

バックエンドでの処理は困難です。さらに、文書化することは困難です。 1つのアクションにはリクエストのさまざまなバリエーションがあり、それらはすでに特定のレスポンスに依存しています。

すべてのソリューションには重大な欠点があります。

インターネットでREST=についての多くの記事を読みました。どこでも理論のみです。私の特定の問題にどのように参加するのですか?

32
stalxed

REST=インターネットに関する多くの記事を読みました。

私がここで見るものに基づいて、あなたは本当にREST and DDDに関するジム・ウェバーの講演の少なくとも1つを見る必要があります

しかし、残りのユースケースで何をすべきか?

しばらくAPIを無視します-HTMLフォームでどのようにしますか?

おそらくDealの表現を提示するWebページがあり、その上に多数のリンクがあります。 1つのリンクからHoldDealフォームに移動し、別のリンクからChangePriceフォームに移動します。これらのフォームにはそれぞれ、入力するフィールドが0個以上あり、フォームはそれぞれリソースに投稿してドメインモデルを更新します。

彼らはすべて同じリソースに投稿しますか?おそらく、そうではないでしょう。それらはすべて同じメディアタイプを持つため、同じWebエンドポイントにすべて投稿する場合は、反対側でコンテンツをデコードする必要があります。

そのアプローチを前提に、システムをどのように実装しますか?さて、あなたの例に基づいて、メディアタイプはjsonになりたいと思っていますが、実際にはそれ以外の部分に問題はありません。

1)動詞を使用する:

それはいいです。

しかし!動詞はurl(in REST theory)では使用できません。

いいえ。 RESTはリソース識別子のスペルを気にしません。動詞が悪いことを主張するURIのベストプラクティスがたくさんあります-それは本当です-しかし、それはRESTに続くものではありません。

しかし、人々があまりにもうるさい場合は、動詞の代わりにコマンドのエンドポイントに名前を付けます。 (つまり、「hold」は動詞ではなく、ユースケースです)。

すべての操作に1つのPUT要求を使用します。

正直なところ、それも悪くありません。ただし、PUTメソッドを指定する方法のために、uriを共有することは避けますが、クライアントが一意の識別子を指定できるテンプレートを使用します。

ここが重要です。HTTPおよびHTTP動詞の上にAPIを構築しています。 HTTPはドキュメント転送向けに設計されています。クライアントは、ドメインモデルで要求された変更を説明するドキュメントを提供し、ドメインに変更を適用し(または適用しない)、新しい状態を説明する別のドキュメントを返します。

CQRSボキャブラリーから少し借りて、ドメインモデルを更新するコマンドを投稿しています。

PUT /commands/{commandId}
{ 
   'deal' : dealId
   'action': 'HoldDeal',
   'params': {'reason': 'test'}
}

正当化-特定のコマンド(特定のIDを持つコマンド)をコマンドキュー(コレクション)に入れます。

PUT /rest/{version}/deals/{dealId}/commands/{commandId}
{ 
   'action': 'HoldDeal',
   'params': {'reason': 'test'}
}

ええ、それでも結構です。

RESTBucksをもう一度見てください。これはコーヒーショップのプロトコルですが、すべてのapiは、ステートマシンを進めるために小さなドキュメントを渡すだけです。

30
VoiceOfUnreason

ドメインレイヤーとは別にREST APIを設計します。

ドメイン駆動設計の重要な概念の1つは、異なるソフトウェアレイヤー間の低結合です。そのため、残りのAPIを設計するとき、あなたが持つことができる最高の残りのAPIについて考えます。次に、ドメインオブジェクトを呼び出して必要なユースケースを実行するのがアプリケーション層の役割です。

あなたが何をしようとしているのかわからないので、あなたのためにあなたの残りのAPIを設計することはできませんが、ここにいくつかのアイデアがあります。

私が理解しているように、あなたにはDealリソースがあります。あなたが言ったように、作成/削除は簡単です:

  • POST/rest/{version}/deals
  • / rest/{version}/deals/{id}を削除します。

次に、取引を「保留」します。私はそれが何を意味するのか分かりません、あなたはそれがリソース「取引」で何が変わるかについて考えなければなりません。属性を変更しますか?はいの場合、Dealリソースを変更しているだけです。

PUT/rest/{version}/deals/{id}

{
    ...
    held: true,
    holdReason: "something",
    ...
}

それは何かを追加しますか?取引で複数の保留を設定できますか? 「保留」は名詞であるように思えます。い場合は、より良い名詞を見つけます。

POST/rest/{version}/deals/{id}/holds

{
    reason: "something"
}

別の解決策:forget REST理論。URLで動詞を使用することでAPIがより明確で効率的で単純になると思う場合は、ぜひ実行してください。それを避ける方法ですが、もしそれができないのであれば、それが普通だからといってsomethingいことをしないでください。

Twitterのapi を見てください。多くの開発者は、Twitterには適切に設計されたAPIがあると言っています。タダー、動詞を使う!クールで使いやすい限り、誰が気にしますか?

あなたのためにあなたのAPIを設計することはできません、あなたはあなたのユースケースを知っている唯一の人ですが、私は2つのアドバイスをもう一度言います:

  • 残りのAPIを単独で設計し、アプリケーション層を使用して適切なドメインオブジェクトを正しい順序で呼び出します。それがまさにアプリケーション層の目的です。
  • 盲目的に規範や理論を守らないでください。はい、可能な限り良い慣行と規範に従うように努めるべきですが、もしそれを残せないなら(もちろん慎重に検討した後)
15
Kaidjin

記事 RESTful APIを介したCQRSの公開 は、問題に対処する詳細なアプローチです。 プロトタイプAPI を確認できます。いくつかのコメント:

  • これは洗練されたアプローチであるため、おそらく記事のすべてを実装する必要はありません。HTTPのETagおよびIf-Matchによるイベントソーシングの同時実行は、このような「高度な」機能です。
  • これは意見のあるアプローチです。DDDコマンドタイプは、ボディではなくメディアタイプヘッダーを介して送信されます。個人的には、私はそれが面白いと思います...しかし、この方法を実装するのは確かではありません
2

ユースケース(UC)をコマンドとクエリ(CQRS)の2つのグループに分け、2つのRESTコントローラー(1つはコマンド用、もう1つはクエリ用)を持っています。REST POST/GET/PUT/DELETEの結果、リソースに対してCRUD操作を実行するためにリソースはモデルオブジェクトである必要はありません。リソースは任意のオブジェクトにできます。実際、DDDではドメインを公開しないでください。コントローラーのモデル。

(1)RestApiCommandController:コマンドのユースケースごとに1つのメソッド。 URIのRESTリソースはコマンドクラス名です。コマンドを作成し、コマンドバス(私の場合はメディエーター)を介して実行するため、メソッドは常にPOSTです。リクエスト本文は、コマンドプロパティ(UCの引数)をマップするJSONオブジェクトです。

例えば: http://localhost:8181/command/asignTaskCommand/

@RestController
@RequestMapping("/command")
public class RestApiCommandController {

private final Mediator mediator;    

@Autowired
public RestApiCommandController (Mediator mediator) {
    this.mediator = mediator;
}    

@RequestMapping(value = "/asignTaskCommand/", method = RequestMethod.POST)
public ResponseEntity<?> asignTask ( @RequestBody AsignTaskCommand asignTaskCommand ) {     
    this.mediator.execute ( asigTaskCommand );
    return new ResponseEntity ( HttpStatus.OK );
}

(2)RestApiQueryController:クエリのユースケースごとに1つのメソッド。ここで、URIのRESTリソースは、クエリが返すDTOオブジェクトです(コレクションの要素、または1つだけ)。メソッドは常にGETであり、クエリのパラメータUCはURIのパラメーターです。

例えば: http://localhost:8181/query/asignedTask/1

@RestController
@RequestMapping("/query")
public class RestApiQueryController {

private final Mediator mediator;    

@Autowired
public RestApiQueryController (Mediator mediator) {
    this.mediator = mediator;
}    

@RequestMapping(value = "/asignedTask/{employeeId}", method = RequestMethod.GET)
public ResponseEntity<List<AsignedTask>> asignedTasksToEmployee ( @PathVariable("employeeId") String employeeId ) {

    AsignedTasksQuery asignedTasksQuery = new AsignedTasksQuery ( employeeId);
    List<AsignedTask> result = mediator.executeQuery ( asignedTasksQuery );
    if ( result==null || result.isEmpty() ) {
        return new ResponseEntity ( HttpStatus.NOT_FOUND );
    }
    return new ResponseEntity<List<AsignedTask>>(result, HttpStatus.OK);
} 

注:メディエーターはDDDアプリケーション層に属します。それはUC境界であり、コマンド/クエリを探し、適切なアプリケーションサービスを実行します。

0
choquero70