2016-02-29 10 views
20

私のプロジェクトでは、DDD方法論を使用しています。残りのAPIと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で使用できません(REST理論では)。

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に関する多くの記事を読んでいます。私の特定の問題でここにいる方法はどこでもただの理論ですか?

+1

私は答えとして次のように述べたくないので、おそらく他の人が恐ろしいアイデアの場合に意見を述べる可能性があります。 '/ rest/{version}/dealsheld /'、 '/ rest/{version}/dealscompleted/{id}'などのように、あなたはどのような状態で扱っているのか知る必要があります。そのような計画は意味をなさないでしょうか? –

答えて

16

私はインターネット上のRESTに関する多くの記事を読んでいます。私は、あなたが本当に、少なくともRESTのジム・ウェバーの会談の一つとDDD

しかし、何を監視する必要があり、ここで見たものに基づいて

残りのユースケースとは関係がありますか?

APIをしばらく無視してください.HTMLフォームではどうしますか?

あなたはおそらくそれ上のリンクの束で、プレゼントにディールの表現をWebページを持っていると思います。 1つのリンクはHoldDealフォームに、もう1つのリンクはChangePriceフォームに移動します。これらのフォームにはそれぞれ0個以上のフィールドがあり、フォームはドメインモデルを更新するためにいくつかのリソースにポストします。

彼らはすべて同じリソースに投稿しますか?多分、おそらくそうではないでしょう。彼らはすべて同じメディアタイプを持っているので、すべて同じWebエンドポイントに投稿していれば、相手側のコンテンツをデコードする必要があります。

このアプローチを考えると、どのようにシステムを実装していますか?メディアの種類はあなたの例に基づいてjsonになりたいと思っていますが、それ以外の部分は本当に間違っていません。

1)使用動詞:大丈夫です

しかし!動詞はURLで使用できません(REST理論では)。

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

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

使用するすべての操作の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はステートマシンを前進させるために小さなドキュメントを渡しています。

+2

RESTに基づいてリモートプロシージャコールを作成したようです。 – xfg

+1

しかし、ドメインモデルの動作に従う20個のエンドポイントを構築したくない場合はどうすればよいですか? 20エンドポイントは維持するのが非常に困難です。適切なドメインの動作をトリガーするために、アプリケーション層とドメイン層の間に1つのエンドポイントと追加のレイヤーがあり、投稿されたデータを比較して処理する場合はどうでしょうか? – mko

7

あなたの残りのapiをドメインレイヤーとは独立してデザインします。

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

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

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

  • は、POST /休憩/ {バージョン}/
  • DELETE /休憩/ {バージョン} /プラン/ {ID}扱っています。

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

PUT /休憩/ {バージョン} /取引/ {ID}

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

それが何かを追加していますか?ある取引で複数の保有契約を結ぶことはできますか? 「ホールド」は名詞であると私に聞きます。醜い場合は、より良い名詞を見つけてください。 REST理論を忘れ:

POST /休憩/ {バージョン} /取引/ {idが}/

{ 
    reason: "something" 
} 

別のソリューションを保持しています。 apiがurlの動詞を使うことでより明確で、より効率的で、簡単になると思うならば、ぜひそれをしてください。あなたはおそらくそれを避ける方法を見つけることができますが、できない場合は、ちょうどそれが標準であるために醜い何かをしないでください。

twitter's apiを参照してください。多くの開発者は、Twitterにうまく設計されたAPIがあると言います。 Tadaa、動詞を使用しています!誰も気にせず、クールで使い易い限り?

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

  • は、それ自体でのREST APIを設計し、アプリケーション層を使用して適切なドメインオブジェクトを正しい順序で呼び出します。それはアプリケーション層がここにあるのとまったく同じです。
  • 規範と理論を盲目的に守らないでください。可能であれば、良い慣行と規範に従うようにしてください。しかし、それを残すことができない場合(コースを慎重に検討した上で)
+1

ドメイン駆動型デザインはドメインに関するものです。 APIクライアントはドメインを考慮して設計する必要があります。それ以外の場合は、DDDの利点のほとんどを失います。 –

+0

これは、ドメインのすべての複雑さをAPIのコンシューマーに公開する必要があるわけではありません。 APIは、例えば、ドメイン層の機能のサブセットを公開することができる。 – Kaidjin

+0

コマンドハンドラやその他の内部のロジックを公開しないでください。しかし、これは集約コマンドについてです。すべての複雑さではなく、ドメインの公的な形です。 –

0

REST URLで動詞を使用するかどうかは矛盾しています主題ただし、動詞を使用したくない場合は、いつでもPUTを/rest/{version}/dealsにして、クエリパラメータ/rest/{version}/deals/{id}?action=holdを追加することができます。同じパターンの後、PUTリクエスト本体の一部をActionにすることができます。

あなたのアプリケーション層はより特定のコマンドを作成し、それをディスパッチして、ドメインロジックが別のコマンドハンドラに分離されたままになるようにします。

+0

PUTは、すでに存在するいくつかのリソースに対してアクションを実行するべきだと言っている場合、POSTでなければなりません。 – roarsneer

+0

@bkhlは誰ですか? 'PUT'は冪等でなければならないのですが、結果が同じであればなぜ修正を適用できないのですか? –

+0

RESTのPUTは、新しい表現をアップロードしてリソースを更新するためのものです。 http://restcookbook.com/HTTP%20Methods/put-vs-post/短い説明があります – roarsneer

0

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

(1)RestApiCommandController:コマンドごとに1つのユースケース。 URIのRESTリソースはコマンドクラス名です。メソッドは常にPOSTです。コマンドを作成した後、コマンドバス(私の場合はメディエーター)を使用して実行します。リクエスト本文は、コマンドプロパティ(UCのargs)をマップする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オブジェクト(コレクションの要素、または単独の要素)です。このメソッドは常にGETであり、クエリUCのパラメータはURIのparamsです。例えば

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境界であり、コマンド/クエリを探し、適切なアプリケーションサービスを実行します。