REST设计用于不同的操作

时间:2014-08-12 01:34:16

标签: web-services rest

假设我们有一个名为时间表的实体。为简单起见,我们假设 Timesheet 实体有三个属性( TimesheetID 状态小时)。

我们目前允许时间表对象的所有者通过此终点提交 Timesheets -

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

{UserID}目前是时间表对象的所有者。使用{TimesheetID},我还可以追溯数据库中时间表的所有者。

好的,现在问题就在这里 -

现在,我们希望让他们的经理能够将员工的时间表对象更新为该报告(例如批准/拒绝 Timesheets 更改状态,或覆盖小时)。

但是,管理员权限有不同级别。某些管理员只允许更新状态,其中一些管理员可以同时更新状态时间

我应该重复使用现有的终点来进行常规用户提交和管理员更新(我更喜欢)吗?或者我应该为每个“经理级别”创建一个不同的终点?

如果我希望重用所有用户提交和管理器更新的现有端点,我该如何处理错误,例如管理器是否配置为仅更新状态,但 TimeSheet 对象同时更改了状态小时。我是否应该回复403并提供详细的错误说明,告诉管理员您无法更改 Hours 属性或更新状态并忽略小时

3 个答案:

答案 0 :(得分:3)

如果您关注HATEOAS constraint,那么时间表资源上的GET将提供当前可用于互动的超媒体控件(链接和表单)用它。虽然这可以通过多种方式完成,但提供最低形式耦合的方式将包括它们有效参数的形式。

对于您的示例,我将包含两个具有相同端点/Users/{UserID}/Timesheets/{TimesheetID}的经理表单。第一个将状态作为必填字段,第二个将具有状态状态可以是可选的)和小时(需要几小时)。

如果提交者不允许提交小时,您可以让第二个表单以403 Forbidden回复。或者,您可以过滤GET中包含的表单,以便仅显示允许用户提交的表单。

<强>更新

例如GET /Users/1234/Timesheets/24目前可能会产生

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

要应用HATEOAS约束,我们需要添加超媒体控件。我们暂时忽略这些URL,因为它们是实现细节。这可能会给我们类似的东西

<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>

表单的作用(以及如何识别表单)是媒体类型中记录的内容。例如:

  

时间表资源上的cancel表单将取消时间表,将其状态更新为“已取消”。取消时间表后,将无法再更新批准或拒绝时间表。

同样在媒体类型中,您将记录资源的属性。如,

  

Timesheet 资源具有以下属性:

     
      
  • TimesheetID 时间表的唯一标识符
  •   
  • 状态时间表的当前状态。状态可能包括   
        
    • 已提交时间表已提交但未获批准
    •   
    • 已批准时间表已获批准
    •   
    • 已拒绝时间表已被拒绝
    •   
    • 已取消时间表已取消
    •   
  •   
  • 小时时间表记录的小时数(小数)
  •   

虽然这可以由模式指定,但应该避免这样做,因为这样做可能很难在以后扩展资源。例如,您可能决定添加“WeekEnding”日期属性。现有的调用者不应该关心新属性,如果他们只是选择他们感兴趣的数据就可以了。但是,如果你已经指定了架构而没有扩展的想法,那么添加属性会导致调用者出现验证错误添加属性时。

现在,就谁能做我们有几个选择而言。一种选择是仅包含所有控件,并使用403响应对调用者未授权调用的任何请求。另一种选择是过滤控件,因此他们只能看到他们可以调用的控件。例如对于可以批准/拒绝的经理,他们可能会收到以下回复

<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>

可以更新小时的经理可以

<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>

或者,您可以包含所有用户的所有表单,但添加一个他们无权调用它的指示符。例如对于无法更新小时数的经理:

<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>

我更喜欢这种后续方法,因为您最终没有支持API调用,开发人员抱怨某个特定表单不存在。无论哪种方式(包括或过滤),如果来电者调用不允许的表格,您仍然会回复403

有点偏离主题,但为了完整性,HATEOAS真正脱颖而出,因为它根据资源的当前状态传达有效的超媒体控件集。例如,当时间表被取消时,批准/拒绝或更新它不再有效,因此取消的时间表上的GET可能会返回

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

这清楚地告知调用者,此特定时间表不允许进一步操作。

你会注意到的另一件事是我们还没有实际指定任何或URL。它们可能全部相同(例如/Users/{UserID}/Timesheets/{TimesheetID})或者它们可能不同(例如selfUrl = /Users/{UserID}/Timesheets/{TimesheetID},updateURL = /Users/{UserID}/Timesheets/{TimesheetID}/update等。)

最终调用者不应该关心,因为它将使用表单/链接中的任何内容。这为您提供了极大的灵活性,因为您可以根据实施需要进行更改。例如,如果您使用的是Command Query Responsibility Segregation(CQRS),那么将GET个请求发送到//readonly.myserver.com/Users/{UserID}/Timesheets/{TimesheetID}POSTPUT和{{1}可能是有意义的请求DELETE

答案 1 :(得分:1)

是的,请使用403 Forbidden。我匹配你描述的场景。 RFC 7231说:

  

403(禁止)状态代码表示服务器已理解      请求但拒绝授权。希望的服务器      公开为什么禁止请求可以描述      响应有效负载中的原因(如果有的话)。

作为替代方案,服务器可以执行允许当前用户的那些操作,并忽略其他所有操作。如果这是一个好主意取决于你的观点。我更愿意拒绝整个请求并返回403 Forbidden

答案 2 :(得分:1)

1)您使用POST进行创建,因此您可以在同一端点上使用PUT来更新数据(在请求的内容中提供新数据)。要限制/记录更新数据的人,您可以将其用户/经理ID作为查询参数或正文

传递

2)403 Forbidden sound更好,以便让用户(经理)更清楚发生了什么,而不是让他认为数据已正确更新但实际上只是部分更新