我正在参与一个项目,其中一些高级团队成员认为REST API必须符合HATEOAS并实现所有Richardson的成熟度级别(http://martinfowler.com/articles/richardsonMaturityModel.html)!
AFAIK大多数REST实施都不符合HATEOAS,因此应该有一个充分的理由说明为什么更多的人不这样做。我可以想到诸如增加复杂性,缺乏框架(服务器和客户端),性能问题以及...等原因。
你怎么看?您在现实项目中对HATEOAS有任何经验吗?
我正在参与一个项目,其中一些高级团队成员认为REST API必须符合HATEOAS并实现所有Richardson的成熟度级别(http://martinfowler.com/articles/richardsonMaturityModel.html)!
AFAIK大多数REST实施都不符合HATEOAS,因此应该有一个充分的理由说明为什么更多的人不这样做。我可以想到诸如增加复杂性,缺乏框架(服务器和客户端),性能问题以及...等原因。
你怎么看?您在现实项目中对HATEOAS有任何经验吗?
Answers:
REST社区中没有人说REST很简单。HATEOAS只是使REST体系结构增加难度的方面之一。
人们出于您建议的所有原因而没有使用HATEOAS:这很困难。它增加了服务器端和客户端的复杂性(如果您确实想从中受益)。
但是,今天有数十亿人体验到REST的好处。您知道亚马逊的“结帐” URL是什么吗?我不。但是,我每天都可以结帐。该网址是否已更改?我不知道,我不在乎。
你知道在乎吗?编写屏幕的任何人都会抓取Amazon自动化客户端。那些可能会不厌其烦地嗅探网络流量,阅读HTML页面等内容的人,以查找何时以及使用哪些有效负载进行调用的链接。
而且,一旦亚马逊改变了内部流程和URL结构,这些硬编码的客户端就失败了,因为链接断开了。
然而,休闲的网络冲浪者能够整天购物几乎没有障碍。
这就是REST的作用,人类可以理解和理解基于文本的界面,识别带有购物车的小图形,并理解其实际含义。
大多数编写软件的人都不会这样做。大多数编写自动客户端的人不在乎。大多数人发现,在客户休息时修复他们的客户要比设计应用程序而不是一开始就更容易。大多数人根本就没有足够的客户。
如果您正在编写内部API,以在流量的两端都具有专家技术支持和IT部门的两个系统之间在两个系统之间进行通信,并且他们能够快速,可靠地按照变更时间表进行变更,那么REST不会给您带来任何好处。您不需要它,您的应用程序还不够大,而且寿命不足以解决问题。
具有大量用户群的大型站点确实存在此问题。他们不能仅仅要求人们在与系统交互时立即改变客户代码。服务器开发时间表与客户端开发时间表不同。对API进行突然更改对于所有相关人员来说都是无法接受的,因为这会中断双方的流量和操作。
因此,这样的操作很可能会从HATEOAS中受益,因为它更易于版本控制,更易于旧客户端迁移,更容易向后兼容。
将客户端的大部分工作流程委派给服务器并根据结果采取行动的客户端比不这样做的客户端对服务器更改的鲁棒性要强得多。
但是大多数人不需要这种灵活性。他们正在为2或3个部门编写服务器代码,这些代码全部在内部使用。如果发生故障,他们会对其进行修复,并将其纳入其正常操作中。
无论是来自REST还是其他任何方式,灵活性都滋生了复杂性。如果您希望它简单,快速,那么您就不能使其变得灵活,您只需“做到”就可以完成。当您添加抽象并取消对系统的引用时,工作变得更困难,样板增加,要测试的代码更多。
大部分REST失败了“您将不需要它”的要点。当然,直到您这样做。
如果您需要它,请使用它,并按布局使用它。REST不会通过HTTP来回传递东西。从来没有,它的水平要高得多。
但是当您确实需要REST并且确实使用REST时,则HATEOAS是必需的。它是程序包的一部分,是使程序完全起作用的关键。
是的,我在API的超媒体方面有一些经验。这里有一些好处:
可探索的API:听起来很琐碎,但不要低估可探索API的功能。浏览数据的能力使客户端开发人员更容易构建API及其数据结构的思维模型。
内联文档:使用URL作为链接关系可以将客户端开发人员指向文档。
简单的客户端逻辑:仅遵循URL而不是自己构造URL的客户端应该更易于实现和维护。
服务器拥有URL结构的所有权:超媒体的使用消除了客户端对服务器使用的URL结构的硬编码知识。
将内容卸载到其他服务:将内容卸载到其他服务器(例如CDN)时,需要超媒体。
带有链接的版本控制:Hypermedia帮助API的版本控制。
相同服务/ API的多个实现:当存在相同服务/ API的多个实现时,超媒体是必需的。例如,服务可以是具有用于添加帖子和评论的资源的博客API。如果服务是根据链接关系而不是硬编码的URL指定的,则同一服务可以在不同的URL多次实例化,这些URL由不同的公司托管,但单个客户仍可以通过定义明确的一组链接进行访问。
您可以在以下位置找到这些要点的深入说明:http : //soabits.blogspot.no/2013/12/selling-benefits-of-hypermedia.html
(这里有一个类似的问题:https : //softwareengineering.stackexchange.com/questions/149124/what-is-the-benefit-of-hypermedia-hateoas,我给出了相同的解释)
理查森的成熟度等级3很有价值,应该采用。约恩·怀尔德(JørnWildt)已经总结了一些优点,威尔特的另一个答案很好地补充了它。
但是,理查森的成熟度级别3与菲尔丁的HATEOAS不同。理查森的成熟度级别3仅与API设计有关。Fielding的HATEOAS也是关于API设计的,但是还规定客户端软件不应假定资源具有超出媒体类型定义的结构的特定结构。这就需要一个非常通用的客户端,例如Web浏览器,它不具有有关特定网站的知识。由于Roy Fielding创造了REST一词,并将HATEOAS设置为遵守REST的要求,因此问题是:我们是否要采用HATEOAS,如果不是,我们是否仍可以将我们的API称为RESTful?我想我们可以。让我解释。
假设我们已经实现了HATEOAS。现在,应用程序的客户端非常通用,但是很可能会带来糟糕的用户体验,因为在没有任何关于资源语义的知识的情况下,无法对资源的表示进行调整以反映这些语义。如果资源“汽车”和资源“房屋”具有相同的媒体类型(例如application / json),则它们将以相同的方式呈现给用户,例如作为属性表(名称/值对)呈现。
但是,好的,我们的API是真正的RESTful。
现在,假设我们在此API之上构建了第二个客户端应用程序。第二个客户违反了HATEOAS的想法,并具有关于资源的硬编码信息。它以不同的方式显示汽车和房屋。
API仍可以称为RESTful吗?我认同。API的其中一个客户端违反了HATEOAS并不是错误。
我建议构建RESTful API,即从理论上可以实现通用客户端的API ,但是在大多数情况下,为了满足可用性要求,您需要一些有关客户端资源的硬编码信息。不过,请尝试尽量减少硬编码,以减少客户端和服务器之间的依赖关系。
我在我的REST实现模式中包括关于HATEOAS的部分,称为JAREST。
我们正在构建REST级别3 API,我们的响应在HAL-Json中进行。HATEOAS对于前端和后端都非常有用,但同时也带来了挑战。我们还进行了一些自定义/添加,还用于在HAL-Json响应中管理ACL(不违反HAL-Json标准)。
我看到的HATEOAS的最大优点是,我们不需要在前端应用程序中编写/猜测任何url。您只需要一个入口点(https://hostname
),然后就可以使用响应中提供的链接或模板链接浏览资源。这样,可以轻松处理版本控制,重命名/替换url,以其他关系扩展资源而不会破坏前端代码。
使用自我链接在前端缓存资源是小菜一碟。我们还通过套接字连接将资源推送到客户端,因为这些资源也是在HAL中呈现的,因此我们可以轻松地以相同的方式添加它们以缓存。
使用HAL-Json的另一个优点是,很明显响应模型应该是什么样的,因为应该遵循一个成文的标准。
我们的一个自定义的是,我们增加了一个行动自链接对象的内部对象是哪些动作或CRUD操作验证的用户被允许在各自的资源执行暴露给前端(create:POST
,read:GET
,update:PUT
,edit:PATCH
,delete:DELETE
)。像这样,我们的前端ACL完全由我们的REST API响应决定,从而将这一责任完全转移到了后端模型上。
因此,举个简单的例子,您可以在HAL-Json中有一个post对象,看起来像这样:
{
"_links": {
"self": {
"href": "https://hostname/api/v1/posts/1",
"actions": {
"read": "GET",
"update": "PUT",
"delete": "DELETE"
}
}
},
"_embedded": {
"owner": {
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com",
"_links": {
"self": {
"href": "https://hostname/api/v1/users/1",
"actions": {
"read": "GET"
}
}
}
}
},
"subject": "Post subject",
"body": "Post message body"
}
现在,我们在前端要做的就是AclService
用一种isAllowed
方法构建一个,该方法检查我们要执行的动作是否在actions对象中。
当前在前端,它看起来很简单: post.isAllowed('delete');
我认为REST级别3很棒,但它可能会引起一些麻烦。您将需要对REST有深入的了解,如果您想使用3级REST,我建议您严格遵循REST概念,否则在实现它时很容易迷路。
就我们而言,我们的优势在于我们同时在前端和后端进行构建,但原则上不应该有所作为。但是我在我们的团队中看到的一个常见陷阱是,一些开发人员试图通过更改后端模型来解决前端问题(体系结构),以使其“适合”前端需求。