Play框架,REST,路由和控制器

3

我已经阅读了一些关于RESTful API设计和背后的概念非常清晰的有趣教程...但现在让我们通过Play将其实践。

假设我们想要实现一个提供与用户打交道功能的RESTful API。让我们从模型开始。这是Address类:

case class Address(
  id: Int,
  street: String,
  zip: String,
  city: String,
  country: String
)

这里是User类:

case class User(
  id: Int,
  email: String,
  firstName: String,
  lastName: String,
  addresses: Array[Int]
  // addresses: Array[Address] would this option be better?
)

...最后是路由:

# Creates a new user
POST   /users                     controllers.users.create

# Gets the user identified by the specified id
GET    /users/:userId             controllers.users.find(userId)

# Modifies the user identified by the specified id
PUT    /users/:userId             controllers.users.update(userId)

# Deletes the user identified by the specified id
DELETE /users/:userId             controllers.users.delete(userId)

第一个问题是:如何通过电子邮件检索用户并保持我的API符合REST规则?以下方法行不通,因为它与“GET users /:userId”冲突:
# Gets the user identified by the specified email address
GET    /users/:email              controllers.users.findByEmail(email)

我心目中有两个选择,它们分别是:
GET    /users                     controllers.users.list(Option[email])

或者

GET    /users/:email/xxx          controllers.users.findByEmail(email)

在这里,xxx 应该是一种 虚拟资源。对此有什么建议吗?

我的第二个问题也是最后一个问题:我应该如何管理用户地址?是获取一个 User,将新的 Address 添加到 User.addresses,然后使用 PUT 更新 User 吗?

PUT    /users/:userId             controllers.users.update(userId)

我应该像这样创建一个特定的控制器来管理用户地址吗?

POST   /users/:userId/addresses/  controllers.addresses.create(userId)

个人而言,我更喜欢第二个选项...但也许还有更好的选择。

3个回答

1

从您的问题中并不清楚您是想实现“获取”还是“查找”。通常情况下,如果使用电子邮件不存在,则“获取”将返回404错误,但我希望“查找”返回200并显示无结果。个人建议使用以下方式:

GET    /users/find          controllers.users.find(email: Option[String])

回答第二个问题取决于您计划仅更新地址还是始终更新整个用户。例如,您可以拥有一个包含所有用户详细信息和地址的巨大表单的网页,并使用一个HTTP请求保存所有信息。
或者另一个观点是地址是一个独立的资源还是始终与用户一起使用(创建/读取/更新/删除)。

是的...但是RESTful URL不应该包含动词,而只能包含用于标识资源的名词。 - j3d
我不会那么严格,因为最终结果是相同的。您可以通过电子邮件地址获取用户。但如果您真的不想在URL中使用动词,那么可以有两个URL:GET /users controllers.users.find(email: Option[String])GET /user/:userId controllers.users.get(userId) - Rado Buransky

0
以下代码无法正常工作,因为它与GET users/:userId冲突。
如果您的用户ID在此版本的REST API生命周期内永远不会是有效的电子邮件地址(这是当前类型的情况),那么它仍然可以工作,因此您的代码可以区分它们。当然,如果您想放松未来的假设(例如,如果您想使用户ID成为任意字符串),或者如果您决定不喜欢将两种类型的查询组合在一起,您始终可以引入新的REST API版本。
我应该获取一个用户,将新地址添加到User.addresses中,然后使用PUT更新用户吗?
默认情况下,这不是事务性的。但是,如果您使用ETags,如果同时有其他内容更改了用户,则可以拒绝用户更新。

是的,我可以使用类似于这样的正则表达式修改路由:GET /users/$email/< ^ [a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$> controllers.users.findByEmail(email)。关于我的第二个问题… 我更多地谈论了设计(一个控制器与两个控制器[一个用于用户,另一个用于地址])。 - j3d
路由的想法很有趣。不过似乎不太易读,我个人更喜欢只有一个路由,并在控制器中进行区分。 - Robin Green

0

第一个问题是:如何通过电子邮件检索用户并保持我的API符合REST规则?

我会简单地说GET /users/email/:email controllers.users.findByEmail(email),如果存在给定电子邮件的用户,则返回200,否则返回404。

我可以理解这种方法的吸引力,但我会在@Robin的想法上进行争论。从技术上讲,这是可行的,但由于我对REST的资源标识元素的解释,它不符合RESTful的感觉。此外,将两个或多个可能性合并为服务器上的标识符以消除歧义,这使我感到代码脆弱,最终迫使我在周末工作,因为随着需求的变化,用户标识符将来来去去,迫使我一遍又一遍地修改控制器端点。我也可能有偏见,因为我看到了像findByNamefindByEmail等规范方法名称,但从未看到过findByYourGuessIsAsGoodAsMine

我的第二个也是最后一个问题是:我应该如何管理用户地址?我应该获取一个User,将新的Address添加到User.addresses中,然后使用PUT更新User吗?还是应该创建一个专门用于管理用户地址的控制器,像这样?

很明显,用户是有趣的资源,而不是电子邮件(请注意,我在脑海中使用电子邮件来区分物理地址而不是地址)。我的路由看起来会像users/:userId/emails/一样,并且要在Users控制器中进行管理。从软件工程的角度或意识形态REST的角度来看,我认为没有理由增加额外的类。


第一个答案:您的建议很好。那么Robin Green提出的选项呢?即让控制器确定输入是ID还是电子邮件?第二个答案:你说的比对了...也许我在我的问题中不够清楚 :-) 功能问题对我来说很清楚,即我希望用户能够添加尽可能多的地址,但不是在注册时强制执行(POST users/:userId/addressses)。我的困境只是如何组织控制器:controllers.Users.addAddress vs controllers.Addresses.create。 - j3d
看我的编辑。在我看来,这里没有明显的科学或客观答案,而更多地是实用的软件工程角度。 - Vidya
好的,我明白了...不过我的第二个问题是关于物理地址...而不是电子邮件(请参见我帖子中的示例)。无论如何,感谢您宝贵的评论 :-) - j3d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接