用于创建多个关联的webservice标准方法

时间:2016-08-15 21:05:37

标签: rest restful-architecture restful-url api-design httpverbs

我正在寻找针对特定其他Web服务用例的端点设计的建议。

我们有一个基本API可以将特定用户注册到特定课程中:

PUT /rest/courses/{courseId}/enroll/{studentId}

这很好用,而且很简单。

但是,我们通常有N个用户,我们想要注册到同一个课程,其中N可能是1到30.我看到两个可能的选项来完成这个批量注册:

  1. 调用上述端点N次
  2. 实施单独的端点,如下所示:

    PUT /rest/courses/{courseId}/enroll
    
  3. ...并将N个学生ID放在请求正文中。

    选项1似乎效率不高。选项2闻起来有点不合适(对于PUT来说似乎不对,但对于POST来说也是如此)。这些选项中的任何一个对你来说都显得更好(更“安静”)吗?或者是否有明确的第三种,更好的选择?

    谢谢!

2 个答案:

答案 0 :(得分:3)

什么是资源表示?

使用PUT /rest/courses/{courseId}/enroll/{studentId} - 正如你所说的那样,它是直截了当的并且可以正常工作,但鉴于REST是REpresentational State Transfer,总是问自己REST的一个问题是 - 表示的资源状态是什么在那个URI?

e.g。如果客户端要调用GET /rest/courses/{courseId}/enroll/{studentId},将返回什么资源表示?它只是学生,还是他们在该课程中注册的一些细节? (例如包括报名日期)?使用动词' enroll'在资源上调用GET是否有意义?在里面?同一个学生可以多次报名参加同一课程(即如果他们在以后的学期重读课程吗?)

另一种可能性是:

POST /rest/courses/{courseId}/enrollments
Body:
{
  "studentId" : xyz
}

这将返回状态代码201 Created,并导致在某个位置创建资源,并将Location标头设置为该URI,例如

/rest/enrollments/{enrollmentId}

如果您使用相同的StudentId执行了另一个POST,您的业务逻辑将检查是否允许这样做,例如给定注册的学期,如果请求无效,则返回400,如果有效,则返回201 Created with 2nd Enrollment。

此惯例还允许您定义:

GET /rest/courses/{courseId}/enrollments

可以返回该课程中所有注册的列表,

GET /rest/enrollments

可以返回所有课程中所有注册的列表(如果有帮助的话)。

PUT to collections

要回到原来的问题,如果您的URI应该是一个'集合'资源,从技术上讲,PUT应该取代整个集合 - 因为它应该在语义上意味着客户端正在说"这是我希望存储在这个资源URI中的表示" - 正如你猜测的那样,它没有正确的气味。

在单个请求中POST多个记录

但是,POST没有这样的约束,实际上非常灵活。您可以定义API以允许BODY包含导致创建多个资源的多条指令,例如

POST /rest/courses/{courseId}/enrollments
BODY
{
  students : [
    {
      studentId : 100
    },
    {
      studentId : 101
    }
  ]
}

然而,允许这样做会引入一些复杂性:

  • 已创建资源的可发现性 - 响应只能包含一个位置标头,因此客户端无法找到所有已创建的注册。您必须在POST中包含响应正文中的注册URI列表,而不是使用Location标题
  • 处理部分失败 - 您如何表明添加一名学生是否失败,但另一名学生成功了?您必须有一个包含错误消息的正文,这也不是RESTful,理想情况下您应该使用400或500作为错误条件......或者您必须以原子方式处理批量添加,如果有任何失败,确保没有处理过(例如数据库回滚)
  • 您可能需要定义提交中的记录数量限制,或者至少是正文的有效负载大小,以免您打开API以承担Slow Http Attack的风险,具体取决于需要多长时间处理每条记录。对此的一个解决方案可能是使用202 Accepted并异步处理请求 - 但这假设您有后端基础结构来支持异步处理。

一种新型操作 - 新资源?

您的问题提到您认为即使使用POST,将多个学生提交到.../enroll端点似乎也无法恢复。我认为这部分是因为enroll(和enrollment声音就像他们只与一个学生有关。另一种方法是定义新资源 - bulkEnrollment。例如而

POST /rest/courses/{courseId}/enrollments

意味着'使用POST数据创建注册,并且只接受一个学生,一个新资源:

POST /rest/courses/{courseId}/bulkEnrollments

意味着使用POST数据创建批量注册'并接受多个学生ID。批量注册本身成为您跟踪的实体,然后可以在以下位置检索其资源表示:

GET /rest/bulkEnrollments/{bulkEnrollmentId}

该表示将列出该特定批量操作创建的所有注册,包括可能有关批量注册本身的一些元数据,包括执行该注册的经过身份验证的用户以及何时执行。

超媒体/ HATEOAS

无论您是否公开bulkEnrollment资源,也可能值得考虑将超媒体引入您的表示 - 例如注册集合中的每个条目都可以包含指向个人注册的资源的超媒体链接(例如/rest/enrollments/{enrollmentId},有时称为self链接),每个注册可以包含指向该注册的超媒体链接。表示学生的资源(例如/rest/students/{studentId})。

事实上,超媒体(例如Hal)是您可以采用的最RESTful技术 - 远比您在端点中使用的特定动词/名词或结构重要。通过仅让客户端通过跟踪链接之前的请求与其发现的端点进行交互,您的API可以自我描述资源之间的关系。它还为实际URI端点结构随时间变化提供了很大的灵活性。但最重要的是,它允许您在给定资源的当前状态的情况下向客户端发出关于实际允许哪些操作的信号。这个概念被称为HATEOAS(该示例使用Xml,但Hal可以帮助Json中使用相同的概念),并且对于“3”级别来说是必需的。 REST API根据REST API的Richardson Maturity Model

答案 1 :(得分:0)

PATCH(建议的RFC 6902标准)作为一种替代选择,可以看起来像这样:

{
    "op" : "add",
    "path" : "/rest/courses/{courseId}/students",
    "value" : ["studentId1", "studentId2"]
}