简体   繁体   English

REST设计用于不同的操作

[英]REST Design for different actions

Say we have a entity called Timesheet . 假设我们有一个名为Timesheet的实体。 For simplicity, let's assume the Timesheet entity has three properties ( TimesheetID , Status and Hours ). 为简单起见,我们假设Timesheet实体有三个属性( TimesheetIDStatusHours )。

We currently allows the owners of Timesheet objects to submit their Timesheets via this end point - 我们目前允许Timesheet对象的所有者通过此终点提交他们的Timesheets -

   POST: /Users/{UserID}/Timesheets/{TimesheetID}

With {UserID} currently is the owner of the Timesheet object. {UserID}目前是Timesheet对象的所有者。 With {TimesheetID} I can also trace back the owner of the Timesheet in the database. 使用{TimesheetID},我还可以追溯数据库中Timesheet的所有者。

OK, now here is the question - 好的,现在这里是问题 -

Now we'd like to have their managers to be able to update the Timesheet objects of the employees to that report to them (such as approve/reject Timesheets by changing the Status , or override Hours ). 现在,我们希望让他们的经理能够将员工的Timesheet对象更新为该报告给他们(例如通过更改状态批准/拒绝Timesheets ,或覆盖小时 )。

However, there are different levels of Manager Permissions. 但是,管理员权限有不同级别。 Some managers are only allow to update Status and some of them are allowed to update both Status and Hours . 一些管理员只允许更新状态 ,其中一些管理员可以更新状态小时

Should I reuse the existing end point for both regular user submissions and managers update (I prefer)? 我是否应该重复使用现有的终点来进行常规用户提交和管理员更新(我更喜欢)? Or should I create a different end point for each of the "Manager Level"? 或者我应该为每个“经理级别”创建一个不同的终点?

If I hope to reuse the existing end point for all user submissions and manager updates, how do I handle the errors such as if a manager is configured to only update the Status , but the TimeSheet object posted to the REST service has both Status and Hours changed. 如果我希望重用所有用户提交和管理器更新的现有端点,如何处理错误,例如,如果管理器配置为仅更新状态 ,但发布到REST服务的TimeSheet对象同时具有状态小时改变。 Should I respond with a 403 with a detailed error description to tell the Manager that you can't change the Hours property or update the Status and ignore Hours ? 我是否应该回复403并提供详细的错误说明,告诉管理员您无法更改“ 小时”属性或更新状态并忽略小时数

If you're following the HATEOAS constraint , then a GET on the Timesheet resource will provide the hypermedia controls (links and forms) that can currently be used to interact with it. 如果您遵循HATEOAS约束 ,那么Timesheet资源上的GET将提供当前可用于与之交互的超媒体控件(链接和表单)。 While this can be done a number of ways, the one that provides the lowest form of coupling, will include in the form they valid parameters. 虽然这可以通过多种方式完成,但提供最低形式耦合的方式将包括它们有效参数的形式。

For your example, I would include two manager forms with the same endpoint /Users/{UserID}/Timesheets/{TimesheetID} . 对于您的示例,我将包含两个具有相同端点/Users/{UserID}/Timesheets/{TimesheetID}管理器表单。 The first one would have a status as a required field, the second one would have status ( status could be optional) and hours (hours would be required). 第一个将具有作为必填字段的状态 ,第二个具有状态状态可以是可选的)和小时 (需要小时)。

You could then either have the second form respond with 403 Forbidden if the submitter is not permitted to submit hours. 如果提交者不允许提交小时,您可以让第二个表单以403 Forbidden响应。 Or alternatively, you could filter the forms included in the GET , so that only the forms the user is permitted to submit are shown. 或者,您可以过滤GET包含的表单,以便仅显示允许用户提交的表单。

Update 更新

For example and GET on /Users/1234/Timesheets/24 might currently produce 例如, /Users/1234/Timesheets/24上的GET当前可能会产生

<Timesheet>
    <TimesheetID>24</TimesheetID>
    <Status>Submitted</Status>
    <Hours>40</Hours>
</Timesheet>

To apply the HATEOAS constraint, we need to add the hypermedia controls. 要应用HATEOAS约束,我们需要添加超媒体控件。 We'll ignore the URLs for the moment, because they are implementation details. 我们暂时忽略这些URL,因为它们是实现细节。 This might give us something like 这可能会给我们类似的东西

<Timesheet>
    <TimesheetID>24</TimesheetID>
    <Status>Submitted</Status>
    <Hours>40</Hours>
    <link rel="self" href="{{selfUrl}}"/>
    <form id="approve" action="{{approveUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Approve"/>
        </Status>
    </form>
    <form id="reject" action="{{rejectUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Reject"/>
        </Status>
    </form>
    <form id="update" action="{{updateUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Approve"/>
            <option value="Reject"/>
        </Status>
        <Hours type="decimal" cardinality="required"/>
    </form>

    ... there might be other forms too, like ...

    <form id="cancel" action="{{cancelUrl}}" method="DELETE"/>
</Timesheet>

What the forms do (and how to recognise a form) are what get's documented in the media type. 表单的作用(以及如何识别表单)是媒体类型中记录的内容。 For instance: 例如:

The cancel form on a Timesheet resource will cancel the timesheet, updating it's status to "Cancelled". 时间表资源上的cancel表单将取消时间表,将其状态更新为“已取消”。 Once a Timesheet has been cancelled, it will no longer be possible to update approve or reject the timesheet. 取消时间表后,将无法再更新批准或拒绝时间表。

Also in the media type you would document the properties of the resources. 同样在媒体类型中,您将记录资源的属性。 eg, 例如,

The Timesheet resource has a the following properties: Timesheet资源具有以下属性:

  • TimesheetID A unique identifier for the timesheet TimesheetID时间表的唯一标识符
  • Status The current status of the timesheet. 状态时间表的当前状态。 Status may include 状态可能包括
    • Submitted The timesheet has been submitted, but not approved 提交的时间表已提交,但不获批准
    • Approved The timesheet has been approved 已批准时间表已获批准
    • Rejected The timesheet has been rejected 拒绝时间表已被拒绝
    • Cancelled The timesheet has been cancelled 已取消时间表已取消
  • Hours The number of hours (decimal) recorded for the timesheet 小时时间表记录的小时数(小数)

While this could be specified by a schema, this should be avoided as doing so can may it very difficult to extent the resources later on. 虽然这可以由模式指定,但应该避免这样做,因为这样做可能很难在以后扩展资源。 For instance, you might decide to add a "WeekEnding" date property. 例如,您可能决定添加“WeekEnding”日期属性。 Existing callers should not care about the new property, which is fine if they are just plucking out the data they are interested in. However, if you've specified the schema without thoughts of extension, then adding properties can cause validation errors in the callers when you add properties. 现有的调用者不应该关心新属性,如果他们只是选择他们感兴趣的数据就可以了。但是,如果你已经指定了架构而没有扩展的想法,那么添加属性会导致调用者出现验证错误添加属性时。

Now, in terms of who can do what we have a couple of options. 现在,就谁能做我们有几个选择而言。 One option is to just include all the controls and respond with 403 for any requests made that the caller is not authorised to invoke. 一种选择是只包括所有控件,并对403响应调用者未被授权调用的任何请求进行响应。 Another option is to filter the controls, so they can only see the ones they can invoke. 另一种选择是过滤控件,因此他们只能看到他们可以调用的控件。 eg for the manager that can approve/reject, they might get the following response 例如,对于可以批准/拒绝的经理,他们可能会收到以下回复

<Timesheet>
    <TimesheetID>24</TimesheetID>
    <Status>Submitted</Status>
    <Hours>40</Hours>
    <link rel="self" href="{{selfUrl}}"/>
    <form id="approve" action="{{approveUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Approve"/>
        </Status>
    </form>
    <form id="reject" action="{{rejectUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Reject"/>
        </Status>
    </form>
</Timesheet>

Whereas a manager that can update the hours might get 可以获得可以更新时间的经理

<Timesheet>
    <TimesheetID>24</TimesheetID>
    <Status>Submitted</Status>
    <Hours>40</Hours>
    <link rel="self" href="{{selfUrl}}"/>
    <form id="approve" action="{{approveUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Approve"/>
        </Status>
    </form>
    <form id="reject" action="{{rejectUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Reject"/>
        </Status>
    </form>
    <form id="update" action="{{updateUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Approve"/>
            <option value="Reject"/>
        </Status>
        <Hours type="decimal" cardinality="required"/>
    </form>
</Timesheet>

Alternatively, you can include all the forms for all the users, but add an indicator that they are not authorised to invoke it. 或者,您可以包括所有用户的所有表单,但添加一个他们无权调用它的指示符。 eg for the manager that can't update hours: 例如,对于无法更新小时数的经理:

<Timesheet>
    <TimesheetID>24</TimesheetID>
    <Status>Submitted</Status>
    <Hours>40</Hours>
    <link rel="self" href="{{selfUrl}}"/>
    <form id="approve" action="{{approveUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Approve"/>
        </Status>
    </form>
    <form id="reject" action="{{rejectUrl}}" method="PUT">
        <Status cardinality="required">
            <option value="Reject"/>
        </Status>
    </form>
    <form id="update" action="{{updateUrl}}" method="PUT" authorised="false">
        <Status cardinality="required">
            <option value="Approve"/>
            <option value="Reject"/>
        </Status>
        <Hours type="decimal" cardinality="required"/>
    </form>
</Timesheet>

I prefer this later approach as you don't end up with support calls for your API, with developers complaining that a particular form doesn't exist. 我更喜欢这种后来的方法,因为你最终没有得到API的支持调用,开发人员抱怨某个特定的表单不存在。 Either way (included or filtered), if the caller invokes a form they are not permitted to, you would still respond with 403 . 无论哪种方式(包括或过滤),如果调用者调用不允许的表单,您仍然会回复403

A bit off topic, but for completeness, HATEOAS really comes to the fore because it communicates the valid set of hypermedia controls based on the current state of the resource. 有点偏离主题,但为了完整性,HATEOAS真正脱颖而出,因为它根据资源的当前状态传达有效的超媒体控件集。 eg, when a Timesheet has been cancelled, it's not longer valid to approve/reject or update it, so a GET on a cancelled Timesheet might return 例如,当取消时间表时,批准/拒绝或更新时间表不再有效,因此取消时间表上的GET可能会返回

<Timesheet>
    <TimesheetID>24</TimesheetID>
    <Status>Cancelled</Status>
    <Hours>40</Hours>
    <link rel="self" href="{{selfUrl}}"/>
</Timesheet>

This clearly communicates to the caller that no further actions are permitted on this particular Timesheet . 这清楚地告知调用者在此特定时间表上不允许进一步的操作。

The other thing you'll have noticed is that we haven't actually specified any or the URLs yet. 你会注意到的另一件事是我们还没有实际指定任何或URL。 They might all be all the same (eg /Users/{UserID}/Timesheets/{TimesheetID} ) or they might be different (eg selfUrl= /Users/{UserID}/Timesheets/{TimesheetID} , updateURL= /Users/{UserID}/Timesheets/{TimesheetID}/update , etc). 它们可能都是相同的(例如/Users/{UserID}/Timesheets/{TimesheetID} )或者它们可能不同(例如selfUrl = /Users/{UserID}/Timesheets/{TimesheetID} ,updateURL = /Users/{UserID}/Timesheets/{TimesheetID}/update等)。

Ultimately the caller should not care as it will use whatever is in the form/link. 最终调用者不应该关心,因为它将使用表单/链接中的任何内容。 This provides you with great flexibility as you can change these to suit your implementation needs. 这为您提供了极大的灵活性,因为您可以根据实施需要进行更改。 For instance, if you are using Command Query Responsibility Segregation (CQRS), it might make sense to send GET requests to //readonly.myserver.com/Users/{UserID}/Timesheets/{TimesheetID} and POST , PUT and DELETE requests to //readwrite.myserver.com/Users/{UserID}/Timesheets/{TimesheetID} . 例如,如果您正在使用命令查询责任隔离 (CQRS),那么将GET请求发送到//readonly.myserver.com/Users/{UserID}/Timesheets/{TimesheetID}以及POSTPUTDELETE请求可能是有意义的。到//readwrite.myserver.com/Users/{UserID}/Timesheets/{TimesheetID}

Yes, go with 403 Forbidden . 是的,请选择403 Forbidden I matches the scenario you describe. 我匹配你描述的场景。 RFC 7231 says: RFC 7231说:

The 403 (Forbidden) status code indicates that the server understood the request but refuses to authorize it. 403(禁止)状态代码表示服务器理解请求但拒绝授权。 A server that wishes to make public why the request has been forbidden can describe that reason in the response payload (if any). 希望公开请求被禁止的服务器可以在响应有效负载中描述该原因(如果有的话)。

As an alternative, the server could perform those actions the current user is allowed to and ignore everything else. 作为替代方案,服务器可以执行允许当前用户的那些操作并忽略其他所有操作。 If this is a good idea depends on your scenarion. 如果这是一个好主意取决于你的观点。 I woul prefer to deny the whole request and return 403 Forbidden . 我更愿意否认整个请求并返回403 Forbidden

1) You use POST to create, so you can use PUT on the same endpoint to update the data (providing the new data in the content of the request). 1)您使用POST进行创建,因此您可以在同一端点上使用PUT来更新数据(在请求的内容中提供新数据)。 To limit/log who updates the data you can pass their user/manager ID as a query parameter, or inside the body 要限制/记录谁更新数据,您可以将其用户/经理ID作为查询参数或在正文内部传递

2) 403 Forbidden sound better so that it's more clear for the user(manager) what has happened instead of letting him think the data was updated correctly but it was only partially updated actually 2)403 Forbidden sound更好,以便用户(经理)更清楚发生了什么,而不是让他认为数据已正确更新但实际上只是部分更新

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM