2

我正在设计一个安静的服务来支持一个现有的向导,它允许用户提交一个新资源(让我们称之为customer),但是是分段的。

此向导在用户提交时对每个页面进行验证,但仅对用户正在提交的页面进行验证。只有当用户选择提交客户进行最终处理时,它才会对整个对象进行完整的验证。

为了简化向导,并允许我们在添加更多字段时在维护版本中调整 UI,我们没有将向导的结构编码到资源中。客户不会按照向导呈现数据的方式“汇总”。

以资源的命名子文档不一定分层显示在该资源的完整文档中(或至少以不同的方式)的方式设计 RESTful 服务是否很奇怪?

假设我的向导页面是:

  • 联系信息
  • 食物偏好
  • 恐惧清单

然后这是一个示例客户对象:

// Note that the wizard page groupings don't show up explicitly
{
    customer: {
        firstName: "Pilsner",
        lastName: "Dopplebock",
        emailAddress: "nextguest@hotelcalifornia.com",
        addressLine1: "123 Fleece Place",
        addressLine2: ""
        town: "Ibinjad",
        region: "North Dakota",
        postalCode: "12123",
        homePhoneNumber: "2123234124",
        faxPhoneNumber: null,
        meatPreference: "well-done",
        allergies: "shellfish",
        fears: [
            "banshees",
            "baths",
            "sleeveless shirts"
        ]
    }
}

假设我的资源基本 URL 是:

http://www.somewhere.com/customers
http://www.somewhere.com/customers/{id}

创建以下宁静的 URL/方法会是奇怪的还是错误的,即使它们customer实际上并没有按照它们暗示的方式进行细分?

http://www.somewhere.com/customers/contactinformation (POST)
http://www.somewhere.com/customers/{id}/contactinformation (POST, or PUT for update? maybe GET)
http://www.somewhere.com/customers/{id}/foodpreference (POST, or PUT for update?, maybe GET)
http://www.somewhere.com/customers/{id}/fears (POST to add a single item?, maybe PUT for a batch?, maybe GET)

如果我一次没有全部资源,我曾考虑使用备用向导 URL,但在我看来,这似乎不适合以资源为导向:

http://www.somewhere.com/customerwizard/submitcontactinformation (POST)
http://www.somewhere.com/customerwizard/{customer-id}/submitcontactinformation
http://www.somewhere.com/customerwizard/{customer-id}/submitfoodpreference
http://www.somewhere.com/customerwizard/{customer-id}/fears

count(可能是第二个问题,虽然是相关的):拥有一个不一定出现在主集合上的集合风格资源的子属性是否很奇怪?我想这样做以支持分页视图...

http://www.somewhere.com/customers/count (GET)
4

3 回答 3

0

我不认为这个问题与引用的问题重复。您不是要求对现有资源执行部分更新,而是要求服务器验证“部分”资源(我认为这本身就是整个资源,而不是您以后通常会呈现给用户的资源)。

在这种情况下,REST 不一定是正确的选择。REST 旨在优化对潜在分布式受众多次访问的静态或半静态资源的读取访问。为了提供帮助,您能否回答以下问题:

  1. 您是否想在初始请求-响应通信完成后获取验证结果?
  2. 您是否曾两次提交相同的数据(来自不同用户或来自同一用户)?
  3. 如果是这样,响应是否总是相同的,即验证算法是确定性的吗?
  4. 是否可以将所有这些未加密且公开可见的数据发送给其他人?

如果您对所有这些回答都是肯定的,那么 REST 非常适合。如果您对这三个问题都回答“”,那么 REST 就不合适了。喜忧参半的回答有些混淆了决定。

如果我处于你的位置,并且没有更多数据,我会首先将其实现为 HTTPS 上的 RPC 接口,直到我了解数据缓存和安全要求是什么,所以我会知道系统的哪些部分会受益于缓存(仅在最终用户的机器上或作为未加密传输的公共资源,并且可由中间人缓存。

有一个很棒的资源,称为基于 HTTP 的 API 的分类,它可能有助于确定 REST 是否真的是您想要在 API 设计中遵循的路径。请记住,选择替代设计并没有什么“错误”,这是一种权衡。根据各自的优点和缺点做出明智的决定。

于 2013-01-23T11:26:46.243 回答
0

URL之类/customers/{id}/contactinformation的并不陌生。您可能想问自己的一个问题是,在写入时将 Customer 实体分解为单独的片段是否有意义这一事实是否也意味着在读取时单独提供它们可能会更好。它肯定会使任何 HTTP 缓存更加明智。例如,如果您 PUT 到实体片段然后 GET 父级,则随后对片段的 PUT 只会使片段无效,然后父级可能会提供陈旧的数据。获取一个较小的父实体(它具有指向每个实体片段的链接)然后获取每个片段更简单,在这种情况下,对片段的 PUT 会正确提示后续 GET 检索新副本。

于 2013-01-23T15:40:18.447 回答
0

与 Nicholas Shanks 相比,我认为类似向导的系统符合 REST 背后的理念。在网络上看到这样的交互概念并不少见,即通常应用于结帐页面,在一个页面上输入客户信息,在下一页输入送货地址,在第三页输入您的付款数据,然后是确认页面,确认将触发实际订单。

Jim Webber指出,在 REST 架构中,您主要实现域应用程序协议(客户端将遍历的状态机,如果您愿意的话),客户端将跟随他们获取服务器提供的所有信息,通过链接或类似于 HTML 表单的类似表单的表示。这个概念被概括为 HATEOAS。

因此,上面提到的 REST 架构中的结帐系统可能如下所示:将商品放入购物篮后,服务器会为您提供附加链接,这些链接带有一些链接关系(伪 HAL 表示):

{
    ...,
    "_links": {
        "self": {
            "href": "https://..."
        },
        "create-form": {
            "href": "https://shop.acme.com/checkout-wizard-p1"
        },
        "https://acme.com/rel/checkout": {
            "href": "https://shop.acme.com/checkout-wizard-p1"
        },
        ...
    }
}

URI 本身在这里不是相关的东西,因为 URI 的拼写在 REST 架构中并不重要,只要它符合RFC 3986 (URI)中概述的规则。为简单起见,它也可能以 UUID 而不是checkout-wizard-p1此处给出的名称结束。相反,重点是此处给出的链接关系名称,即create-form符合在IANA注册的标准化链接关系和自定义链接关系,https://acme.com/rel/checkout它遵循Web 链接 (RFC 5988)概述的扩展机制。引入这种间接方式允许服务器用其他形式替换实际的目标 URI,客户端仍然能够完成他们的任务。

不幸的是,与LinkRFC 5988 定义的标头不同,它可以在同一个 URI 上定义多个关系名称(参见示例Link: <.../checkout-wizard-p1>; rel="create-form https://acme.com/rel/checkout"),HAL JSON 仅允许为每个 URI AFAIK 定义一个链接关系名称。

对于任意客户端,链接关系名称只是任意字符串。此外,Web 链接扩展机制不必从一开始就指向人类可读的文档,尽管稍后可能会通过更新或插件添加对此类关系名称的支持。这里的重点是,了解某个链接关系名称的客户端可以采取相应的行动,并使用 URI 来向它发送请求。链接关系可以根据规则引擎来使用,该规则引擎在满足某些条件时触发伴随的 URI,即某个客户端状态的可用性和指向同一 URI 的链接关系的存在,如本例中给定的链接-关系名称。

在请求用于create-formhttps://acme.com/rel/checkout关系的 URI 的内容时,服务器可能会返回一个HAL-FORMS表示,允许客户端输入所需的客户信息:

{
  "_links": {
    "self": {
        "href": "https://shop.acme.com/checkout-wizard-p1"
    },
    "_templates": {
        "default": {
            "contentType": "application/x-www-form-urlencoded",
            "key": "default",
            "method": "POST",
            "properties": [
                { 
                    "name": "firstName",
                    "prompt": "First Name",
                    "readOnly": false,
                    "regex": "^[A-Z][a-z]{1,24}$",
                    "required": true,
                    "templated": false,
                    "value": "",
                    "maxLength": 25,
                    "minLength": 2,
                    "placeholder": "Your first name",
                    "type": "text"
                },
                ...
            ],
            "target": "https://shop.acme.com/tmp/ea2b3fb1-c640-40e2-b16a-f7433dee6ba2",
            "title": "Checkout Wizard - Customer Information"
        }
    }
  }
}

与传统的HTML 表单类似,HAL-FORMS会在发送请求之前向客户端介绍要发送请求的目标 URI、要使用的 HTTP 操作以及将请求编组到的媒体类型。与 HTML 相比,HAL-FORMS 在这里默认为application/json而不是application/x-www-form-urlencoded. 令人惊讶的是,HAL-FORMS 仅支持这两种媒体类型。

资源支持的整体结构也通过properties属性中包含的元素来教授。与 HTML 表单一样,可以定义呈现输入的类型,可以是text, hidden, textarea, search, tel, url, email, password, date, month, week, time, datetime-local,number或. 通过可选元素 UI 控件,例如(多选)选项、复选框和单选按钮也可以表示。rangecoloroptions

一旦客户端输入其数据并将其发送到服务器,服务器就可以简单地响应下一个 HAL-FORMS 表示,要求进一步输入,例如送货地址等。在这里,交互设计师可能倾向于https://shop.acme.com/tmp/ea2b3fb1-c640-40e2-b16a-f7433dee6ba2通过使用 直接更新临时资源(即在这种情况下)PUT,与 HTML 表单相比,HAL-FORMS 支持,尽管这也需要传递所有以前的数据,这使得向导或多或少……多余。下一种方法是使用PATCHas HTTP 操作对临时资源执行部分更新,尽管 HAL-FORMS 目前仅支持application/jsonapplication/x-www-form-urlencoded无法通知服务器有关将临时资源转换为所需结果的(隐式)指令,就像您对application/json-patch+jsonapplication/merge-patch+json所做的那样。

因此,我更愿意坚持在POST这里为每个向导页面创建新的临时资源。通过支持隐藏属性传递所有先前传输的属性将很容易成为可能,尽管不是传递所有数据,只需提供临时资源的 URI 作为隐藏属性就足够了。然后,最后一步可以将这些临时资源中的信息“合并”到一个内聚状态,对受影响的临时资源执行清理,并将信息呈现给用户以进行最后确认,在这种情况下,实际资源被创建。

虽然通过 获得的实际表单资源GET是可缓存的,但临时资源并不是因为它们仅通过不安全的操作进行操作,这无论如何都会使(中间)缓存中的存储表示无效。在此基础上缓存表单资源并不是一个坏主意,因为不会一直引入资源的新属性,因此缓存可以减少将该表示从服务器传输到客户端的开销。如果对表单进行了更新,则对该资源的更新,即通过PUT或上传新版本PATCH,将自动使该资源和具有新版本的服务器客户端的任何存储表示无效,第一次绕过缓存,直到响应已添加到缓存中。

虽然 Nicholas 提到只有在他的所有 4 点都可以回答“是”的情况下才应该使用 REST,其中一些点(例如文档加密)更多地针对传输通道(即通过 HTTP 或 HTTPS 的 TLS 或协商的媒体类型)而不是 REST 架构提出的交互概念,在我的意义上,REST 应该旨在以防您的服务应该持续多年,支持过多的不同客户端并支持未来的发展,例如在资源上引入新字段在路上,而不必担心破坏客户。

长话短说,通过 HATEOAS 设计类似向导的交互确实很有意义,尤其是当使用类似表单的表示来教客户端将数据发送到哪里、使用什么 HTTP 方法以及以哪种表示格式发送数据时。表单还有助于向客户介绍资源具有的受支持属性。使用的棘手部分是如何组合通过向导的不同页面提供的数据并将其呈现给客户端。虽然PUTPATCH开始可能很有吸引力,但从狭隘的角度看,由于媒体类型或 HTTP 操作本身的限制,它们可能并不理想。

于 2021-03-19T00:02:31.967 回答