REST - 复杂的应用程序
Posted
技术标签:
【中文标题】REST - 复杂的应用程序【英文标题】:REST - complex applications 【发布时间】:2010-11-20 18:02:35 【问题描述】:我正在努力将 RESTful 原则应用于我正在开发的新 Web 应用程序。特别是,要成为 RESTful,每个 HTTP 请求都应携带足够的信息,以便其接收者对其进行处理,以与 HTTP 的无状态特性完全一致。
该应用程序允许用户搜索药物。搜索接受过滤器作为输入,例如,返回停药、包括免费治疗等。总共可以应用大约 30 个过滤器。
此外,可以输入患者详细信息,包括患者年龄、性别、当前药物等。
为了保持安静,所有这些信息都应该包含在每个请求中吗?这似乎给网络带来了巨大的开销。此外,对 URL 长度的限制,至少对于 GET 而言,不会使这不可行吗?
【问题讨论】:
【参考方案1】:“过滤器作为资源”是一个完美的技巧。
您可以将过滤器定义放到过滤器资源中,它可以返回过滤器ID。
PUT 是幂等的,因此即使过滤器已经存在,您只需要检测到您之前看过过滤器,就可以为过滤器返回正确的 ID。
然后,您可以向其他请求添加过滤器参数,它们可以获取过滤器以用于查询。
GET /medications?filter=1234&page=4&pagesize=20
我会通过某种规范化过程运行原始过滤器,只是为了获得一个标准化集,例如过滤器“firstname=Bob lastname=Eubanks”与“lastname=Eubanks firstname=Bob”相同。但这只是我。
唯一真正担心的是,随着时间的推移,您可能需要淘汰一些过滤器。如果有人使用缺少或过时的过滤器提出请求,您可以简单地出错。
编辑回答问题...
让我们从基础开始。
简单地说,您想指定一个用于查询的过滤器,但这些过滤器(可能)涉及且复杂。如果是简单的 /medications/1234,这不会有问题。
实际上,您始终需要将过滤器发送到查询。问题是如何表示该过滤器。
REST 系统中的会话之类的基本问题是它们通常是“带外”管理的。例如,当您去创建药物时,您可以 PUT 或 POST 到药物资源,然后您会获得对该药物的引用。
通过会话,您(通常)会取回一个 cookie,或者可能是其他一些表示该会话的令牌。如果您对药物资源的 PUT 也创建了一个会话,那么实际上,您的请求创建了两个资源:一个药物和一个会话。
不幸的是,当您使用 cookie 之类的东西,并且您的请求需要该 cookie 时,资源名称不再是资源的真实表示。现在是资源名称(URL)和 cookie。
所以,如果我对名为 /medications/search 的资源执行 GET,并且 cookie 代表一个会话,并且该会话恰好有一个过滤器,您可以看到该资源名称 /medications 的效果如何/search,根本没用。由于 cookie 和会话以及其中的过滤器的副作用,我没有有效使用所需的所有信息。
现在,您或许可以重写名称:/medications/search?session=ABC123,有效地将 cookie 嵌入到资源名称中。
但是现在您遇到了典型的会话合同,特别是它们的寿命很短。因此,该命名资源不太有用,长期而言,不是没用,只是不太有用。现在,这个查询给了我有趣的数据。明天?可能不是。我会收到一些关于会话消失的严重错误。
另一个问题是会话通常不作为资源进行管理。例如,它们通常是副作用,而不是通过 GET/PUT/DELETE 显式管理。会话也是 Web 应用程序状态的“垃圾堆”。在这种情况下,我们只是希望会话正确填充此请求所需的内容。我们实际上并不知道。同样,这是一个副作用。
现在,让我们稍微改变一下。让我们使用 /medications/search?filter=ABC123。
显然,随便,这看起来是一样的。我们只是将名称从“会话”更改为“过滤器”。但是,正如所讨论的,在这种情况下,过滤器是“一流的资源”。它们需要像药物、JPEG 或系统中的任何其他资源一样创建、管理等。这是关键区别。
当然,您可以将“会话”视为一流资源,创建它们,直接将内容放入其中等。但是您可以看到,至少从清晰的角度来看,“一流”会话是如何对于这种情况,这并不是一个很好的抽象。使用会话,就像去清洁工并交出您的整个钱包或公文包。 “是的,票就在某处,挖出你想要的,把我的衣服给我”,尤其是与像过滤器这样的明确的东西相比。
因此,您可以看到,在 30,000 英尺处,过滤器和会话之间的情况并没有太大区别。但是当你放大时,它们就完全不同了。
使用过滤器资源,您可以选择让它们成为永久的东西。你可以让它们过期,你可以做任何你想做的事情。会话往往具有预先设想的语义:短暂的生命、连接的持续时间等。过滤器可以具有您想要的任何语义。它们与会话所附带的完全不同。
如果我这样做,我将如何使用过滤器?
我会假设我真的不关心过滤器的内容。具体来说,我怀疑我是否会查询“所有按名字搜索的过滤器”。在这个关头,它似乎是无趣的信息,所以我不会围绕它进行设计。
接下来,我将规范化过滤器,就像我上面提到的那样。确保等效过滤器确实是等效的。您可以通过对表达式进行排序、确保字段名全部为大写或其他方式来做到这一点。
然后,我会将过滤器存储为 XML 或 JSON 文档,以更适合/适合应用程序为准。我会给每个过滤器一个唯一的键(自然),但我也会用过滤器存储实际文档的哈希值。
我这样做是为了能够快速找到过滤器是否已存储。由于我正在对其进行规范化,因此我“知道”逻辑等效过滤器的 XML(例如)将是相同的。所以,当有人去 PUT 或插入一个新的过滤器时,我会检查散列以查看它之前是否已存储。我可能会返回多个(当然,哈希可能会发生冲突),所以我需要检查实际的 XML 有效负载以查看它们是否匹配。
如果过滤器匹配,我会返回对现有过滤器的引用。如果没有,我会创建一个新的并返回它。
我也不允许过滤器 UPDATE/POST。因为我分发了对这些过滤器的引用,所以我会让它们不可变,这样引用就可以保持有效。如果我想要按“角色”进行过滤,例如“获取所有过期药物过滤器”,那么我将创建一个“命名过滤器”资源,将名称与过滤器实例相关联,以便实际过滤器数据可以更改,但名称保持不变。
另外请注意,在创建过程中,您处于竞争状态(两个请求试图创建相同的过滤器),因此您必须考虑到这一点。如果您的系统具有高过滤量,这可能是一个潜在的瓶颈。
希望这可以为您澄清问题。
【讨论】:
这是我见过的为什么会话不能很好地融入 REST 风格架构的最佳解释之一。 首先我认为:是的!但是在考虑了一下之后,我怀疑从“过滤器是会话的一部分”到“过滤器是一等公民”所描述的提升是否真的是一个有利的想法。最初的问题是允许用户尝试使用搜索过滤器,这可能会产生他自己的特定过滤器。在我看来,使用像方法这样的会话是解决这个问题的一种非常直接的方法。引入一个新的数据库对象,关心规范表示,关心竞争条件,垃圾收集过滤器,......这合适吗? 使用“客户端发起的临时 PUT 新过滤器并可能生成新 ID”的网站示例有哪些? 嗨,我同意,但我仍然认为对于一些特定的实现,例如需要大量安全性、身份验证和授权的企业业务应用程序,人们不能真正依赖持续时间过长的过滤器允许用户查询应用程序,会话非常适合。如果它是一个免费使用的网络应用程序,那么您描述的过滤器更适合。由于 javascript MV* 框架的日益流行,我看到一些公司开始使用这些框架来开发应用程序,并与会话非常适合的旧 JEE 服务器/应用程序连接。 喜欢这个解释!超级棒!【参考方案2】:为了保持安静,所有这些信息都应该包含在每个请求中吗?
没有。如果看起来您的服务器正在发送(或接收)过多信息,则可能是您尚未确定一个或多个资源。
设计 RESTful 系统的第一步也是最重要的一步是识别和命名您的资源。您将如何为您的系统做到这一点?
根据您的描述,这是一组可能的资源:
用户 - 系统的用户(可能是医生或患者 (?) - 角色 可能需要在此处作为资源公开) 药物 - 瓶子里的东西,但它也可能代表瓶子的种类(数量和内容),或者它可能代表特定的瓶子 - 取决于您是药房还是只是帮助办公桌。 疾病 - 患者可能想要服用药物的病症。 患者 - 可能服用药物的人 推荐 - 一种药物,可能对患者有益,基于他们所患的疾病。然后你可以寻找资源之间的关系;
用户拥有并属于许多角色 药物有并且属于许多疾病 疾病有很多建议。 患者患有并且属于许多药物和疾病(可怜的小伙子) 患者有很多建议 推荐有一个患者和一个疾病具体问题可能不适合您的特定问题,但想法很简单:在您的资源之间创建关系网络。
此时考虑 URI 结构可能会有所帮助,但请记住 REST APIs must be hypertext-driven:
# view all Recommendations for the patient
GET http://server.com/patients/patient/recommendations
# view all Recommendations for a Medication
GET http://servier.com/medications/medication/recommendations
# add a new Recommendation for a Patient
PUT http://server.com/patients/patient/recommendations
因为这是 REST,您将花费大部分时间 defining the media types 在客户端和服务器之间传输您的资源表示。
通过公开更多资源,您可以减少每次请求期间需要传输的数据量。另请注意,URI 中没有查询参数。服务器可以根据需要保持状态以跟踪所有内容,并且每个请求都可以完全独立。
【讨论】:
【参考方案3】:REST 用于 API,而不是(典型)应用程序。不要仅仅因为你在 wikipedia 上读到过,就试图将基本有状态的交互嵌入到无状态模型中。
为了保持安静,所有这些信息都应该包含在每个请求中吗?这似乎给网络带来了巨大的开销。此外,对 URL 长度的限制,至少对于 GET 而言,不会使这不可行吗?
与服务器发送的资源大小相比,参数的大小通常是微不足道的。如果您使用的参数太大以至于它们会成为网络负担,请将它们放在服务器上一次,然后将它们用作资源。
对 URL 长度没有重大限制——如果您的服务器有这样的限制,请升级它。无论如何,它可能已经存在多年并且充满了安全漏洞。
【讨论】:
您能否为您断言 REST 适用于 API 提供参考?我的理解是 REST 是从我认为面向用户的 Web 应用程序的架构中综合而来的。另外,您能否解释一下为什么搜索应用程序是“基本的有状态交互”? REST 是 API 的 RPC 替代方案;它是对 Web 架构的描述。 Web 应用程序在架构上很少与 Web 相似,并且不太适合 RESTful 交互。关于“基本有状态”:从问题来看,过滤器似乎是在一个状态中累积,然后应用于一个集合。对我来说,可以在无状态系统中表示的最佳方式是创建一个“集合”资源并对其应用过滤器——这与 OP 的实现非常不同。 @John 如果你真的对 REST 感兴趣,那么我建议你再去阅读 Roy 的论文。我想你误会了。 @John 我实际上指的是您关于“Web 应用程序在其架构中很少与 Web 相似”的评论。恕我直言,当 Web 应用程序正确完成时,它们看起来与 Web 完全一样。要回答 REST 如何适用于这个搜索问题,请阅读 Will 的回答。 @John 好的,我可以接受。不幸的是,“Web 应用程序”一词在完全没有必要时会自动与使用会话状态相关联,并对安全性、可伸缩性和冗余产生负面影响。【参考方案4】:并非所有这些都必须包含在每个请求中。
每个资源(药物、病史等)都应该有一个唯一标识它的规范 URI。在某些应用程序(例如,基于 Rails 的应用程序)中,这将类似于“/patients/1234”或“/drugs/5678”,但 URL 格式并不重要。
先前已获得资源 URI(例如从搜索或嵌入在另一个资源中的链接)的客户端可以使用此 URI 检索它。
【讨论】:
这是有道理的。因此,当对象可以动态生成时(用户可以创建任意数量的过滤器),听起来每个对象都需要分配一个唯一的 URI。此 URI 将被传递回客户端应用程序。对吗? @alistair77:正确。当用户创建过滤器时,应该返回它的表示形式,以及一个Location
标头以指示可以在何处找到该过滤器以供以后操作。【参考方案5】:
您是否正在开发一个 RESTful API,其他应用程序将使用它来搜索您的数据?或者您正在构建一个以最终用户为中心的 Web 应用程序,用户将在其中登录并执行这些搜索?
如果您的用户正在登录,那么您已经是有状态的,因为您将拥有某种类型的会话 cookie 来维持登录状态。我会继续创建一个包含所有搜索过滤器的会话对象。如果用户未设置任何过滤器,则此对象将为空。
这是一篇关于使用 GET 与 POST 的精彩博文。它提到了 Internet Explorer 设置的 2,048 个字符的 URL 长度限制,因此您希望对长请求使用 POST。
http://carsonified.com/blog/dev/the-definitive-guide-to-get-vs-post/
【讨论】:
以上是关于REST - 复杂的应用程序的主要内容,如果未能解决你的问题,请参考以下文章