不要讨厌HATEOAS

或我如何学会不再担心和爱HATEOAS

REST已成为实现Web服务的事实上的解决方案,至少已成为一种流行的解决方案。 这是可以理解的,因为REST在使用HTTP规范时提供了一定程度的自我文档。 它经久耐用,可扩展,并提供了其他一些理想的特性。

但是,许多所谓的RESTful服务没有实现HATEOAS (作为应用程序状态引擎的超媒体),这会使Roy Fielding晚上 忙 起来 (如果您认为介绍不好,请阅读评论部分 )。 这是一个不幸的趋势,因为包括超媒体控件提供了很多优势,尤其是在客户端与服务器解耦方面。

本文是一个分为两部分的系列文章的第一篇,将介绍控制REST的底层实现细节和设计问题。 我们将讨论在您的RESTful服务中实现HATEOAS值得付出额外的努力,因为您的服务面临着不断变化的业务需求。

第二部分将于3月28日发布,将是一个使用Spring-HATEOAS实现HATEOAS服务的实时代码示例。 在我即将于2016年3月2日星期三在堪萨斯城Spring用户组的主题为“ 我如何学会停止照顾并开始爱上HATEOAS ”的演讲中,您还可以看到其中的一些概念。

REST,成功实现建筑约束的成功故事

作为开发人员,我不得不经常沮丧地学习,以在高位建筑师对我施加的约束下工作。 自从最近向架构师过渡以来,我现在可以定义自己的约束并尽自己的一份力量继续苦难的循环。 但是,在研究本文时,我了解了REST体系结构中经过深思熟虑的约束是如何使其成为Web服务世界的先驱。 苦难的循环至少在这次减少了。

Roy Fielding 在2000年的博士论文中定义了控制REST的六种主要建筑风格约束。 我将详细介绍其中的五个。 第六,按需编码,这是可选的,将不涉及。 五个幸运的样式约束是:客户端-服务器,无状态,可缓存,统一接口和分层体系结构。

1.客户端-服务器

第一种样式约束是客户端-服务器分离。 具有讽刺意味的是,这是当开发人员选择不实施HATEOAS时受到最大影响的约束。

关注点分离是好的系统设计的基本原则之一。 在REST和Web服务的上下文中,这种关注点分离在可伸缩性方面具有一些好处,因为RESTful服务的新实例也不需要处理客户端的拆包。

真正的好处,就像在任何时候都实现关注点分离约束一样,尽管允许独立发展。 客户端处理演示,服务器处理存储。 这种分离意味着对服务器的每次更改都不需要对客户端进行更改(并且不需要协调两者之间的发布),反之亦然。

在本文的后面,我们将更详细地介绍如何不实施HATEOAS来模糊客户端和服务器之间的界限。

2.无状态

如果您要问开发人员RESTful服务的关键特征是什么,您可能会首先收到的答复是它是无状态的。 这是一种流行的响应,因为无状态在REST最令人期望的两个特性中起着核心作用:持久性和可伸缩性。

在这种情况下,无状态意味着每个请求都包含服务器接受或拒绝请求所需的所有信息,并且服务器不需要检查会话状态即可确定请求的有效性。 这将导致持久性,因为客户端不再绑定到特定的服务实例。 如果客户端正在与实例“ A”进行对话并且故障,负载平衡器可以将客户端重定向到另一个可用实例,没有人是明智的。

另一个好处是可伸缩性,因为服务器资源不会因存储用户状态而消耗(如果服务足够流行,则可能会消耗大量资源)。 它还可以响应于流量激增,加快附加服务实例的旋转速度。 也就是说,要实现该功能需要高度的DevOps成熟度。

3.可缓存

第三个样式约束是请求可以是可缓存的。 在这种情况下,可缓存性是指客户端缓存请求的能力。 这与像Redis这样的服务器托管的缓存相反,尽管这是在以后的约束中启用的。 缓存客户端请求是每个主流浏览器中都已实现的功能,可通过使用http标头来激活它,如下图所示(缓存控制)。

图片来源:https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-CN

图片来源:https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-CN

客户端缓存请求的好处是不需要服务器重新提供对未更改且经常访问的资源的响应,从而减少了服务器负载。 同样,由于浏览器比从服务器更快地检索本地缓存的响应,因此可以改善客户端的感知性能。

4.统一的界面

RESTful服务的端点是资源。 状态的变化是通过操纵这些资源而发生的。 发送到这些资源的消息是自描述的,超媒体是应用程序状态的引擎(最后一个约束听起来很熟悉)。

在下面的Richardson成熟度模型部分中,我们将逐步介绍在服务上实现这四个约束的外观。

5.分层架构

像食人魔和洋葱一样,REST体系结构也有层次。 RESTful服务中的分层体系结构是通过通过它发送的消息具有自描述性而实现的,并且每一层都无法从接口看到到下一层。

当我提交在Netflix上观看电影的请求时,无论我使用什么客户端,都将发送GET请求。 该请求可能会到达路由服务。 看到这是一个GET请求(即检索),然后该路由服务可以将该请求发送到服务器缓存。 该缓存可以检查其是否具有与请求的查询匹配的未过期资源。 在我的请求可以实现之前,这可能会持续进行几层甚至在Netflix体系结构中的某些区域。 所有这些路由和重定向都可能发生,因为REST消息是自描述的。 只要层可以理解HTTP,它就可以理解它收到的消息。

理查森成熟度模型

因此,我们已经涵盖了控制REST的六个主要体系结构样式约束中的五个。 现在,让我们仔细研究一下第四个样式约束,即统一界面,如先前所承诺的。 统一的接口定义了RESTful服务的许多“外观”,在该接口中定义了以下端点:GET:/ users / bob。 这也是定义HATEOAS的地方,这就是本文的重点。 为了使这些约束的影响可视化,并了解许多RESTful服务的不足之处,我将以有用的Richardson成熟度模型(RMM)为指南。

POX的沼泽

这是RMM上的级别0。 在这里,服务不能真诚地描述为RESTful。 客户与之连接的端点不是资源,我们在请求中未使用正确的HTTP动词,并且服务器未使用超媒体控件进行响应。 我们都已经在这样的服务上进行了工作,确实有可能(虽然可能不太可能)这种服务易于使用和维护……但是无论它是否绝对不是RESTful的。

通过RMM时,我们将通过通过像Amazon这样的在线零售商订购电视来进行交互,以观察REST中统一接口约束的实现如何影响服务器与客户端之间的交互。

在这里,我们看到了级别0的交互:

POST: viewItem
{“id”: “1234”
}
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00
}
POST: orderItem
{“id” : 1,“items” : [“item” : {“id” : 1234}]
}
Response:
HTTP 1.1 200
{“id” : 1,“items” : [“item” : {“id” : 1234}]
}

资源资源

在RMM的这一级别上,我们正在实现统一接口的前两个约束。 我们正在通过URI(/ items / 1234,/ orders / 1)识别与我们交互的资源,而我们如何通过处理这些资源来与服务交互。

在向我们的服务发送请求时,为我们的每个资源提供专用的端点而不是单个端点,可以为客户与之交互的实体提供更多的标识。 它还为收集分析数据提供了机会,我们的客户如何与我们的服务交互。 热图可以更轻松地显示正在请求哪些资源以及该资源中的特定实体。

POST: /items/1234
{}
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00
}
POST: /orders/1
{“item” : {“id” : 1234}
}
Response: 
HTTP 1.1 200
{“id” : 1,“items” : [“item” : {“id” : 1234}]
}

因此,现在我们要使用资源终结点而不是所有请求都将通过的匿名终结点。 但是,我们与服务交互的性质尚不清楚。 当我们发布到/ items / 1234时,是在创建一个新项目还是要检索? 当我们发布到/ orders / 1时,是在更新现有实体还是创建新实体? 发送请求时,客户端尚不清楚这些交互。

HTTP

到目前为止,我们一直主要使用HTTP作为客户端与RESTful服务进行交互的传输机制。 在此级别,我们将开始使用已定义的HTTP规范。 到目前为止,我们已经使用POST提交了所有请求,现在我们将开始使用更合适的HTTP动词(方法类型)。 这不是一条单向的街道,但是我们的服务器还将响应更合适的状态代码,而不是对每个成功请求都响应200状态代码。

下表列出了RESTful服务通常实现的动词以及对这些动词的一些约束。 如果您不熟悉“幂等”一词(作者曾经),请知道这意味着当执行次数大于零时,执行请求的副作用是相同的。

GET调用应始终返回相同的项目列表。 DELETE请求应删除元素,但后续的DELETE请求应导致服务器状态不变。 注意,这并不意味着响应总是必须相同。 在第二个示例中,第二个DELETE请求可能返回错误响应。 安全表示该操作不会影响服务器的状态。 GET仅是检索,它不会更改其检索资源的状态。 但是,PUT请求可能导致状态更改,因此不是安全动词。

安全 不安全
势力 GET,HEAD,TRACE,选项 删除,放入
不占优势 开机自检


当我们开始在交互中使用正确的HTTP动词和状态代码时,这就是交互的外观:

GET: /items/1234
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00
}
PUT: /orders/1
{“items” : [“item” : {“id” : 1234}]
}
Response: 
HTTP 1.1 226
{“items” : [“item” : {“id” : 1234}]
}

即使不深入了解HTTP规范,客户端与服务器之间的交互也变得更加清晰。 我们正在从服务器获取项目; 我们正在服务器上放置一些东西。 有一些字幕可以帮助您理解HTTP,了解PUT意味着修改会告诉开发人员一个订单已经存在,而我们正在修改它,而不是创建新订单(可能是POST请求)。

理解HTTP状态代码还将使开发人员对服务器如何响应来自客户端的请求有更多的了解。 尽管我们的服务器仍对初始GET请求返回适当的200响应,但服务器现在发送的PUT请求响应为226(已使用IM),这意味着仅返回已更改资源的增量。 如果您在“资源”部分下查看向订单添加商品的响应,则服务器将返回订单ID和商品列表。 在此响应中,仅返回添加到订单中的项目。 如果订单中已经有其他项目,则它们也将在“资源”响应中返回,但在此响应中省略。

或者,如果不存在ID为1234的项目,则HTTP已经定义了正确的响应,而不是返回空的响应正文或某种错误消息。 你能猜出来吗?

GET: /items/1234
Response:
HTTP 1.1 404

超媒体控件

上述为电视下订单的场景为实现超媒体控件带来好处提供了一个很好的用例。 在此情况下,我已经假定用户已经有一个ID为“ 1”的预先存在的订单,但是可能并非总是如此。

在不使用HATEOAS将状态应用程序传达给客户端的情况下,客户端必须足够聪明才能确定用户是否有未结订单,如果有,则确定该订单的ID。 这会导致工作重复,因为确定用户状态的业务逻辑现在同时存在于客户端和服务器上。 随着业务的变化,客户端和服务器之间将具有依赖关系来确定用户订单的状态,客户端和服务器代码都将发生更改,并且需要协调两者之间的发布。 HATEOAS通过其返回的链接告诉客户端状态(即客户端下一步可以做什么)来解决此问题。

GET: /items/1234
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“link” : {“rel” : “next”,“href” : “/orders”}}
}
POST: /orders
{“id” : 1,“items” : [{“id” : 1234}]
}Response:
HTTP 1.1 201:
{“id” : 1,“items” : [{“id” : 1234}
]
links : [{“rel” : “next”,“href” : “/orders/1/payment”}, {“rel” : “self”,“href” : “/orders/1”}]
}

可以省去确定用户是否有有效订单的相对简单性,因为它不够复杂,不足以证明在服务器端实施HATEOAS然后开发可以解释服务产生的超媒体控件的客户端所花的时间(都不其中是微不足道的)。 也就是说,该示例也非常简单,仅代表客户端和服务器之间的一种交互。

死亡,税收和变化,HATEOAS就是这样

开发人员知道,“唯一可以确定的就是死亡和税收”的成语是错误的,第三个可以肯定的是:变化。 任何开发的应用程序都将在其生命周期内发生变化; 添加了新的业务需求,修改了现有业务需求,并一起删除了一些业务需求。

尽管我不希望HATEOAS成为银弹,但我确实相信HATEOAS是为数不多的因遇到现实问题而受益的技术之一。 以下是三个用例的样本,将它们结合在一起使用时,可以与其他可以想象的用例一起,为您说明为什么要在RESTful服务中实现HATEOAS的有力案例。

用例1:管理员和普通用户通过同一客户端进行交互

普通用户和管理员都使用同一客户端与服务进行交互。 在这种使用情况下,普通用户只能在/ items资源上执行GET,但是管理员也将具有PUT和DELETE特权。 如果我们在Richardson成熟度模型(HTTP)的第2级上停止,我们将需要让客户端理解用户所具有的特权类型,以便正确地向用户呈现接口。

使用HATEOAS,可能就像客户端呈现服务器发送的一些新控件一样简单。 这是请求中的差异可能看起来的样子。 另外,我们可能不希望管理员下达订单:

Request:
[Headers]
user: bob
roles: USER
GET: /items/1234
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“links” : [{“rel” : “next”,“href” : “/orders”}]	}
}
Request:
[ Headers ]
user: jim
roles: USER, ADMIN
GET: /items/1234
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“links” : [{“rel” : “modify”,“href” : “/items/1234”},{“rel” : “delete”,“href” : “/items/1234”}]	}
}

用例2:管理员不再可以删除

业务需求发生变化,管理员不再能够删除项目。 在上一个用例中,很可能会说不需要更改客户端(例如,管理员用户将需要一个表单来修改项目的字段),但是删除DELETE动词绝对可以完成而无需更改客户。

随着HATEOAS服务不再返回DELETE链接,客户端将不再将其显示给管理员用户。

Request:
[Headers]
user: jim
roles: USER, ADMIN
GET: /items/1234
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“links” : [{“rel” : “modify”,“href” : “/items/1234”}]	}
}

用例3:用户可以出售自己的商品

现在,企业要求用户具有销售自己的用户商品的能力。 这个用例比前两个更为实际,开始显示出客户端上业务逻辑的数量和复杂性Swift增加,并且还引入了客户端和服务器之间的可能耦合。

用户可以出售自己的物品,但他们也只能只修改自己准备出售的物品。 用户Bob不能修改用户Steve的项目,反之亦然。 解决此问题的常见方法可能是在项目实体内返回一个指定所有权的新字段,但是现在我们正在修改项目,只是为了使我们的客户端可以出于任何业务原因向用户正确呈现界面。

现在,我们正在引入客户端和服务器之间的耦合,并且它们之间的界限很快开始变得模糊。 有了HATEOAS服务,至少对于客户而言,这种复杂性就很多了,并且我们的项目实体保持不变。 以下是一些带有和不带有HATEOAS的示例请求,请注意在HATEOAS示例中,响应看起来与用例1的响应相同。

没有HATEOAS:

Request:
[Headers]
user: jim
roles: USER
GET: /items/1234
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“owner” : “jim” 
}

使用HATEOAS:

Request:
[Headers]
user: jim
roles: USER
GET: /items/1234
Response:
HTTP 1.1 200
{“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“links” : [{“rel” : “modify”,“href” : “/items/1234”},{“rel” : “delete”,“href” : “/items/1234”}]	}
}

摘要

尽管REST的第一个样式约束要求将客户端和服务器之间的关注点分离,但是这种样式约束在不实现HATEOAS的过程中受到了损害。 围绕如何计算用户状态的业务逻辑进行更改意味着需要在客户端和服务器中进行更改。 客户端和服务器的独立可扩展性丢失了(必须同步客户端和服务器的发布),并且重复了业务逻辑。 世界需要更多的HATEOAS来解决这个问题。

参考书目

  • http://roy.gbiv.com/untangled/2008/rest-apis-must-be-超文本驱动
  • http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-745
  • https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
  • http://martinfowler.com/articles/richardsonMaturityModel.html
  • https://zh.wikipedia.org/wiki/No_Silver_Bullet
  • http://www.crummy.com/
  • http://www.crummy.com/writing/speaking/2008-QCon/act3.html

翻译自: https://www.javacodegeeks.com/2016/03/dont-hate-hateoas.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/354576.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端如何进行日志驱动开发

日志在开发过程中的作用自不必说,一旦程序出现问题,我们首先想到的是通过日志监控去追查。 好的日志可以通过应用程序执行的历史记录模拟出用户在使用程序的时候操作的完整过程。 想知道发生了什么 为了便于我们分析程序哪里出现问题,我们…

消息钩子学习工程

前奏近来一直在自学Windows Hook相关的知识,已经尝试多种注入方式。尤其对消息钩子方式很感兴趣,因为看到Spy能够截获系统中绝大多数应用的消息流,就很想知道它的工作原理,打算制作属于自己的Spy。消息钩子简介:消息钩…

[Angular] 笔记 8:list/detail 页面以及@Input

1. list 页面 list/detail 是重要的 UI 设计模式。 vscode terminal 运行如下命令生成 detail 组件: PS D:\Angular\my-app> ng generate component pokemon-base/pokemon-detail --modulepokemon-base/pokemon-base.module.ts CREATE src/app/pokemon-base/p…

javaone_JavaOne 2012 – 2400小时! 一些建议

javaone您可能已经看到JavaOne 2012 Content Catalog在线。 计划委员会经过数周的紧张工作,对每个提案进行了分类,审查,评分和讨论,我们终于设法为您设置了(希望如此)有趣的组合。 整整105天或2400小时&…

推荐几个最近Star过的Github仓库

平时逛Github的时候,总是顺手对一些自己认为好的仓库给个 Star,一是对作者的鼓励,二来推荐给关注自己的人(首页动态可见)。 下面列举了一些我平时 Star 过的仓库,顺便也推荐给我的读者。 Front-End Checkli…

使用Gatling + Gradle + Jenkins Pipeline为您的JAX-RS(和JavaEE)应用程序进行连续压力测试...

在这篇文章中,我将解释如何使用Gatling项目为您的JAX-RS Java EE端点编写压力测试,以及如何将它们与Gradle和Jenkins Pipeline集成,因此,除了进行简单的压力测试外,您还可以使用以下方法: 连续的压力测试&a…

使用 VuePress 搭建一个自己的知识文档

最近准备对前端知识做一个梳理,将自己的平时遇到的问题和解决方案形成一个知识文档。本文记录了搭建 VuePress 的主要过程,同时也提供了部分自定义的配置,示例地址:http://doc.i-fanr.com 环境搭建 VuePress 有着比较完善的中文文…

代码重构学习

文章:浅谈重构中踩过的坑 文章中有一个很好的,优化if else多分支判断的例子,将方法抽象出来,根据不同场景实现抽象类,然后用写一个工厂类,一个工厂方法,通过传入类型,进行实例化。 很…

pta 朋友圈

某学校有N个学生,形成M个俱乐部。每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈。一个学生可以同时属于若干个不同的俱乐部。根据“我的朋友的朋友也是我的朋友”这个推论可以得出,如果A和B是朋友,且B和C是朋友&#…

金三银四跳槽面试季,我整理前端知识做了个网站

每年的金三银四,都将迎来求职面试的一个高峰期,为什么会有那么多的求职需求?多是因为以下几个来源:已拿 offer 等年终奖的:年前已经找到机会,领了年终奖辞职要到新公司报到的临时起意要辞及裸辞的&#xff…

web框架和后台开发_Web开发框架–第1部分:选项和标准

web框架和后台开发在我的公司,我们正在评估未来几年将使用哪种Web开发框架。 自上次评估以来,我们一直在使用由Struts 2驱动的Java应用服务器作为MVC,将Tiles作为模板引擎,将jQuery用于Javascript awesomennes,将DWR用…

hadoop的Map阶段的四大步骤

深入理解map的几个阶段是怎样执行的。转载于:https://www.cnblogs.com/xubiao/p/7846080.html

小程序 Typescript 最佳实践

小程序结合TypeScript开发,如果用第三方框架,首选Taro已完美支持。但是如果你选择原生开发,那么下面的这份实践可能会帮到你。小程序 Typescript 最佳实践使用 gulp 构建(支持 typescript 和 less/sass/scss)使用 type…

DIY注释

从Java 5开始,Java中出现了注释。 我想做一个自己的注释,只是为了看看需要什么。 但是,我发现它们只是接口。 有擦 接口后面没有牙。 必须执行一些代码。 我认为这是橡胶行之有效的方法,我真的找到了解决方法。 首先&#xff0c…

这款电脑升降桌美到我了

一直在寻觅一款集颜值与功能于一体的电脑升降桌,这款乐歌 E5 电动桌终于成功地满足了我的需求。有黑白两款颜色可选,但其中白色钢化玻璃版常适合用来作为白色系桌面的基础——四周圆角设计,再加上碳素钢的桌体框架,整体非常有质感…

spring boot集成mybatis+事务控制

一下代码为DEMO演示,采用注解的方式完成Spring boot和Mybatis的集成,并进行事物的控制 数据源的配置: 1 spring.datasource.urljdbc:mysql://localhost:3306/book 2 spring.datasource.usernameroot 3 spring.datasource.password 4 spring.datasource.d…

分享一个引起极度舒适的工作桌面

干净整洁的桌面或许不能带给你工作效率的提升,但一定会给你带来愉悦的心情。长期码字一定需要一个升降桌,可自由地调节高度,以保证舒适的坐姿和灵活的视角。另外坐久了,累了还能站立工作一会儿。有了外显之后,如果不需…

canvas绘制多边形

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>canvas绘制多边形</title> </head> <body> <canvas id"canvas" style"border: 1px solid darkcyan;" width…

ehcache rmi_EhCache复制:RMI与JGroups

ehcache rmi最近&#xff0c;我正在研究一种需要复制缓存的产品。 缓存提供程序已经确定-EhCache&#xff0c;剩下的就是有关传输的问题。 哪一个是最佳选择&#xff1f; 这里的最佳选择是指性能更好的选择。 仅在两个可用传输之间进行了性能评估-JGroups和RMI&#xff0c;对其…

Element Table 可以实现哪些常见的有用的功能

最近项目中频繁使用 table 功能&#xff0c;因为 UI 框架使用的又是 Element UI&#xff0c;于是总结下在 Element 下 el-table 组件使用技巧。1.行背景色table 组件提供了 row-style 属性&#xff0c;说明&#xff1a;行的 style 的回调方法&#xff0c;也可以使用一个固定的 …