Rest API和DDD

sta*_*xed 35 api rest domain-driven-design

在我的项目中使用DDD方法.

该项目有聚合(实体)交易.这个聚合有很多用例.

对于这个聚合,我需要创建一个rest api.

用标准:创建和删除没问题.

1)CreateDealUseCase(名称,价格和许多其他参数);

POST /rest/{version}/deals/
{ 
   'name': 'deal123',
   'price': 1234;
   'etc': 'etc'
}
Run Code Online (Sandbox Code Playgroud)

2)DeleteDealUseCase(id)

DELETE /rest/{version}/deals/{id}
Run Code Online (Sandbox Code Playgroud)

但是如何处理其他用例呢?

  • 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'
}
Run Code Online (Sandbox Code Playgroud)

但!动词不能在url中使用(在REST理论中).

2)使用完成状态(将在用例之后):

PUT /rest/{version}/deals/{id}/holded
{ 
   'reason': 'test'
}
Run Code Online (Sandbox Code Playgroud)

就个人而言,它看起来很难看.也许我错了?

3)对所有操作使用1 PUT请求:

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

PUT /rest/{version}/deals/{id}
{ 
   'action': 'UnholdDeal',
   'params': {}
}
Run Code Online (Sandbox Code Playgroud)

在后端很难处理.而且,很难记录.由于1个动作具有许多不同的请求变体,因此已经依赖于特定的响应.

所有解决方案都有明显的缺点.

我在网上看过很多关于REST的文章.到处都只有一个理论,如何与我的具体问题在一起?

Voi*_*son 31

我在网上看过很多关于REST的文章.

根据我在这里看到的内容,你真的需要至少观看Jim Webber关于REST和DDD的谈话

但是如何处理其他用例呢?

暂时忽略API - 你将如何使用HTML表单?

你可能有一个网页呈现的表示Deal,上面有一堆链接.一个链接会将您带到HoldDeal表单,另一个链接会将您带到ChangePrice表单,依此类推.这些表单中的每一个都将填充零个或多个字段,并且每个表单都会发布到某个资源以更新域模型.

他们都会发布到相同的资源吗?也许,也许不是.它们都具有相同的媒体类型,因此如果它们都发布到同一个Web端点,则必须解码另一侧的内容.

鉴于这种方法,您如何实施您的系统?嗯,根据你的例子,媒体类型想成为json,但其余部分确实没有任何问题.

1)使用动词:

没关系.

但!动词不能在url中使用(在REST理论中).

不.REST不关心资源标识符的拼写.有一堆URI最佳实践声称动词是坏的 - 这是真的 - 但这不是来自REST的东西.

但是如果人们如此挑剔,你可以命名命令的端点而不是动词.(即:"hold"不是动词,它是一个用例).

对所有操作使用1 PUT请求:

老实说,那个也不错.您不希望共享uri(因为指定了PUT方法),但使用模板,客户端可以在其中指定唯一标识符.

这是肉:你正在HTTP和HTTP动词之上构建一个api.HTTP专为文档传输而设计.客户端为您提供了一个文档,描述了您的域模型中所请求的更改,并将更改应用于域(或不是),并返回描述新状态的另一个文档.

暂时借用CQRS词汇表,您将发布更新域模型的命令.

PUT /commands/{commandId}
{ 
   'deal' : dealId
   'action': 'HoldDeal',
   'params': {'reason': 'test'}
}
Run Code Online (Sandbox Code Playgroud)

理由 - 您将特定命令(具有特定Id的命令)放入命令队列(这是一个集合).

PUT /rest/{version}/deals/{dealId}/commands/{commandId}
{ 
   'action': 'HoldDeal',
   'params': {'reason': 'test'}
}
Run Code Online (Sandbox Code Playgroud)

是的,那也没关系.

再看一下RESTBucks.这是一个咖啡店协议,但所有api只是通过小文件来推进状态机.

  • 我似乎发明了基于REST的远程过程调用. (9认同)
  • 但是,如果您不想构建 20 个遵循域模型行为的端点怎么办?20 端点极难维护。如果您在应用程序层和域层之间有一个端点和一个附加层,可以比较和处理发布的数据以触发正确的域行为,该怎么办? (2认同)
  • 我几乎不认为这是一个全有或全无的情况。我有构建 RESTful API 的经验,几年后它显示出与 OP 类似的迹象,我们公司将其称为 Anemic REST。在我们的例子中,问题在于大型依赖树使得操作成本极高。因此,我想说,如果您可以识别可能不遵守“纯粹”REST 的用例,那就这样吧。以对领域和可维护性有意义的最佳方式来解决它。但无论如何不要完全放弃 REST,在有意义的时候使用它。 (2认同)

Kai*_*jin 15

独立于域层设计您的rest api.

域驱动设计的关键概念之一是不同软件层之间的低耦合.所以,当你设计你的休息api时,你会想到你可以拥有的最好的休息api.然后,应用程序层的作用是调用域对象来执行所需的用例.

我不能为你设计你的休息api,因为我不知道你想做什么,但这里有一些想法.

据我了解,你有一个Deal资源.如你所说,创建/删除很容易:

  • POST/rest/{version}/deals
  • DELETE/rest/{version}/deals/{id}.

然后,你想"持有"一笔交易.我不知道这意味着什么,你必须考虑资源"交易"中的变化.它会改变属性吗?如果是,那么您只需修改交易资源.

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

{
    ...
    held: true,
    holdReason: "something",
    ...
}
Run Code Online (Sandbox Code Playgroud)

它增加了什么吗?您可以在交易中持有多个持有人吗?对我来说,"持有"是一个名词.如果它很难看,找一个更好的名词.

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

{
    reason: "something"
}
Run Code Online (Sandbox Code Playgroud)

另一个解决方案:忘记REST理论.如果你认为你的api会更清晰,更高效,更简单,在网址中使用动词,那么一定要做到这一点.你可能找到一种方法来避免它,但如果你不能,不要做一些丑陋的事情只是因为它是常态.

看看twitter的api:许多开发人员说twitter有一个设计良好的API.Tadaa,它使用动词!谁在乎,只要它很酷且易于使用?

我不能为你设计你的api,你是唯一知道你的用例的人,但我会再说一遍我的两个建议:

  • 设计其余的api,然后使用应用程序层以正确的顺序调用适当的域对象.这正是应用程序层的用途.
  • 不要盲目遵循规范和理论.是的,你应该尽可能地遵循良好的做法和规范,但是如果你不能把它们抛在脑后(仔细考虑当然之后)

  • 域驱动设计是关于域的.API客户端也应该考虑到域的设计.否则你会失去DDD的大部分好处. (3认同)
  • 是的,但这并不意味着您应该将域的所有复杂性暴露给API的使用者.例如,API可以暴露域层的功能的子集. (2认同)