使用 API 创建多个资源(不同类型)的简洁方法

Clean way to create multiple resources (different types) with an API

为了 API 的开发,我们想创建一个新组织,并为该组织创建一个新用户 one 使用干净的表单提交(注册) RESTful API 设计.

由于我们不想混合不同的资源(组织和用户)并通过一次调用创建它们(该调用的响应是组织还是用户?)我们需要将注册分成两部分调用:先创建组织,然后直接创建用户。

但是如果用户和组织的创建被分成两个独立的 API 调用,我们会看到以下问题:

  1. 如何处理错误?例如。如果创建组织成功但由于错误(例如用户电子邮件已存在)导致创建用户失败。在这种情况下,将创建一个没有任何用户的组织,并且没有人可以登录以更改组织资源。
  2. 创建组织后如何进行授权?每个拥有组织 ID 的人都可以在没有任何登录检查的情况下简单地创建一个新用户吗(只能使用电子邮件和密码登录)?或者 create organization return 将创建第一个用户的令牌? (token逻辑会让客户端变得相当复杂:如何处理重投等)

How to handle errors? Eg. if the create organization is successful but the create user fails due to an error (eg. user email already exists). In that case an organization without any user is created and nobody can login to change the organization resource

除了 PATCH none 标准 HTTP 方法真正处理事务行为。 PATCH 即要求补丁文档中定义的所有指令步骤都需要自动处理。必须应用全部或 none 个。因此,如果一条指令失败,则必须应用 none 的修改。然而,PATCH 与其他方法的区别在于,PATCH 应该包含一组指令以应用于一个或多个资源,而其他操作在某种意义上通常针对单个文档。

如果从文档管理系统的角度来看,HTTP 确实是其核心,因为您得出的任何业务规则都只是文档管理的 side-effect(参见 this great talk), the operations of HTTP make a bit more sense. I.e. PUT replaces the current document with the one provided. If none existed so far it is similar to a document creation. The spec here mentions that PUT is allowed to have side-effects. I.e. similar to GIT where a commit will push the head forward though the actual commit will still be accessible via its own URI. DELETE removes the association of the URI to the document stored. Whether that leads to a document removal on the imaginary filesystem or not is an implementation detail. GET will only return the content of that document to the invoker and POST only processes the request according to the service own semantics giving no further promises on what it does with the payload. So whether one or multiple resources are created, or even none at all, is up to the implementation. However, if you create resources you need to return a 201 Created response containing a Location header with the URI of the newly created resource that hints clients about the newly created resource. In the case of multiple resources being created, the spec is a bit less clear what should be returned in that case as only one Location header may appear within the respone

form-based 创建方法的使用肯定是 RESTful 方法,因为这里服务器会教客户端执行任务所需的输入。客户端通常对服务器实际如何处理和存储数据不太感兴趣,它感兴趣的只是请求的完成,无论是成功完成还是提示服务器在处理请求时遇到的问题。

是一次性创建所有内容,还是根据自己的要求划分每个部分,这是您必须做出的选择。 IE。在典型的网站上,您可能会遇到类似向导的方法,您首先将一些与组织相关的信息输入到表单中,单击提交按钮,然后得到进一步的表单响应,要求您输入用户详细信息,并进一步响应总结详细信息并询问确认。数据可以存储在某个临时资源中,在确认摘要后,将一次性创建所有内容,模仿某种原子处理,以防在用户详细信息等方面遇到某些问题时无法创建组织。如果您有多个依赖于先前选择的可选数据,这种方法很方便。

当然,您也可以以一种形式输入数据,然后通过 POST 请求将其发送到服务器,然后以这种方式创建相应的资源。在Locationheader中URL到return又是一个不同的决定。如果要创建的主要资源是我会选择组织 URI 的组织,特别是如果它允许列出其定义的用户。在内部,您可以利用事务来保证组织和用户之间的状态一致,如果无法做到这一点,您可以简单地回滚事务并 return 向用户发送错误。

如果您尝试将创建分成多个步骤,您肯定必须处理用户资源的异常情况及其对组织资源的影响。如前所述,HTTP 没有给出任何关于 HTTP 的提示,这是两个独立且不相关的请求。在这里,不是服务器应该是聪明的东西,而是客户端必须是。如果在创建用户时遇到问题,它应该执行组织本身的清理。在这种情况下,server/API 只是被视为愚蠢的存储。

How to handle the authorization after creating the organization? Can everybody with the organization id simply create a new user without any login check (login is only possible with email and password)? Or will the create organization return a token to create the first user? (The token logic will make the client quite complicated: how to handle resubmissions etc.)

这里主要看你的设计。通常应该在API中添加某种权限管理。一些框架已经包含对此类的支持,即在 Java 和 Spring 生态系统中,您可以将某些注释添加到操作端点和业务方法上,以检查某些分配的用户角色和权限,并且仅在可用时才允许访问.

如果您采用拆分组织和用户创建方法并在用户创建过程中遇到问题,您可以将表单发送回客户端以请求其他用户详细信息,因为其他组织已经存在该信息,直到有效数据 return编辑。现在很多 Web-based APIs 通过邮件发送一些确认 links 来验证 email-address 的正确性并且用户只是“logged-in”的第一个他单击该电子邮件中的激活 link 的时间。在纯 HTTP 中,您将发送包含用户凭据的 HTTP Authorization header。在没有激活用户的情况下,该服务将 return 401 Unauthorized 作为失败阻止用户使用该服务进行身份验证。在这种情况下,外部管理实体(即 API 的项目经理或管理员)必须为该组织创建用户并将数据发送给请求者。但是,IMO 应该高度避免这种状态。在这里,我猜,探查用户的可接受的用户详细信息肯定是更可取的。

您还可以 运行 在未分配给用户的组织达到给定阈值数量后,在后面进行某种清理例程(或执行手动清理任务)以释放资源并避免携带根据您的定义,存在不一致状态,因为必须将用户分配给组织。

如您所见,您有几个设计选择来解决这种情况。无论您进入ata 以一种形式并一次性将所有内容发送到服务器或使用临时资源从 n 个连续的表单请求中收集数据,直到用户确认该数据并且您以原子方式一次性处理所有内容或者您对每个任务使用专用请求并有一些检查系统一致性的支持例程由您决定。

关于您标题中提到的事情的最后说明。 REST 客户端不应将资源视为具有特定类型,因为这会导致客户端期望某些端点 return 某些 types。这也会导致客户端解释 URI 以确定该资源的类型。由于服务器可以随时随意更改其 URI 方案,因此如果开发人员没有将这种知识构建到客户端中,客户端很可能无法根据 URI 自动确定类型。这避免了 REST 架构应该提供的实际好处,即通过客户端与服务器的解耦实现的未来进化的自由。客户端不应使用类型化资源,而应依赖 content-type 协商,其中交换客户端和服务器都理解和支持的标准化表示格式。 media-types 定义这些表示格式指定了有效负载中每个元素的处理规则和语义,并允许互操作性。