Web 服务是否应与网站分开?[已关闭]

2022-09-01 23:20:06

我正在构建一个网站,还想构建一个REST Web服务来访问许多相同的功能(使用google应用程序引擎和spring mvc3),我不确定如何集成/分离这两个部分的最佳实践。

例如,如果我想查看资源,我可以提供以下形式的URL:

{resourcetype}\{resourceid}

对此 URL 的 GET 请求可以在视图上重定向,该视图在客户端基于 HTML/浏览器时生成网页。Spring(从我所读到的 - 尚未尝试过)能够使用相同的资源URL来提供根据内容类型返回HTML / Xml / JSON的视图。这一切似乎都很棒。

POST请求在REST中创建新资源的URL应返回201 CREATED(或者我读过)以及所创建资源的URL,这对于Api来说似乎很好,但似乎与网页中规范的期望略有不同(您可能会被重定向到显示您创建的资源的页面或说它已成功创建或类似的页面)。我是否应该通过在包含用于创建资源的表单的不同URL上提供页面来处理此问题,然后通过ajax提交到Api URL并获取响应并重定向到响应中包含的资源URL。

这种模式似乎可以工作(也应该适用于DELETE),但这是一个好方法,还是我最好将REST Api URL和网站URL分开?这似乎可能会引入相当多的重复和额外的工作。但是拥有单个URL可能意味着您依赖于javascript在支持HTML 5的浏览器的客户端上可用。


答案 1

我建议你把它们分开。通过这样做,您可以获得一些好处。

首先,您将 Web URL 与 API URL 分离,以便它们可以独立更改。例如,您可能需要向后释放对 API 的不兼容更改,在这种情况下,您可以创建一个 /v2/ 目录。同时,你可能想要一个网站上的/about页面,但不需要一个用于你的API的页面。

通过使用不同的 URL,可以简化实现。现在,每个方法都不必确定它是在 JSON/XML 还是 HTML 前面。即使你有一个像Spring这样的框架来完成繁重的工作,这也是正确的。您仍然需要为当前用户的网站做额外的事情。

它还消除了一整类错误。例如,用户在浏览网站时不会重新获得 JSON 或 XML 输出,即使他们具有自定义浏览器匿名性设置也是如此。

您可以轻松地分离用于身份验证的逻辑。对于网站,您需要登录页面和Cookie。对于 API,这些不是必需的,但额外的身份验证标头是必需的(例如,HMAC+sha256 签名)。

最后,通过将站点与 API 分离,可以满足不同的扩展需求。如果您的API受到重创,但网站没有受到重创,则可以在API上投入更多硬件,同时保持网站所需的最少硬件。


更新:为了澄清,我并不是建议你把所有东西都编码两次。有两种不同的方法来查看此内容以消除重复。

首先,用 MVC 的说法,您有一个模型和该模型上的两个不同视图。这就是MVC的全部意义,这样视图和模型就不会捆绑在一起。获取特定资源的代码在两个客户端中是相同的,因此您可以编写模型,以便只有一行代码从数据库或来自任何位置获取该资源。简而言之,您的模型是一个易于使用的库,具有两个客户端。

另一种看待它的方式是,您的网站是公共REST API的第一个客户端;Web服务器实际上调用您的RESTful API来获取其信息。这就是整个吃自己的狗粮原则。


答案 2

我不同意迈克尔的答案,并以此作为我自己答案的基础:

“将你的 Web url 与 API URL 分离,以便它们每个 URL 都可以独立地更改”,就是在面对 REST 时吐口水。很酷的 URI 不会改变。不要担心更改网址。不要使用 URI 对 API 进行版本控制。REST 使用链接来支持 OCP - 在以前版本的 API 上运行的客户端应该很乐意关注上线时存在的链接,而不知道您为增强 API 而添加的新链接。

如果您坚持对 API 进行版本控制,我会要求您使用媒体类型而不是 URI。

接下来,“你简化了你的实现。现在,每个方法都不必确定它是在 JSON/XML 还是 HTML 前面。

如果你这样做,你做错了。来自泽西岛,我从每个方法返回相同的该死的对象,无论我是否生成HTML,XML或JSON。这是一个完全跨领域的问题,由编组人员负责。我使用 VelocityMessageBodyWriter 来发出围绕我的 REST 表示的 HTML 模板。

我的系统中的每个资源都遵循相同的基本行为:

class FooResource extends Resource {
    @GET
    public FooRepresentation get() {
        return new FooRepresentation();
    }
    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response postForm(MulivaluedMap<String, String> form) {
        return post(buildRepresentationFromForm(form));
    }
    @POST
    @Consumes(MediaType.APPLICATION_XML)
    public Resopnse post(FooRepresentation representation) {
         // return ok, see other, whatever
    }
}

GET 方法可能需要构建指向其他资源的链接。这些由 REST API 和 HTML 模板的使用者使用。表单可能必须POST,并且我使用某些特定的链接(由关系定义)作为“操作”属性)。

用户浏览器和用户机器之间通过系统的路径可能不同,但这不是实现的分离 - 它是REST!状态转移按照它需要的方式发生,你如何定义它。用户(在浏览器中)如何继续。

“最后,通过将站点与API分开,您可以满足不同的扩展需求。如果你的API受到重创,但网站没有受到重创,你可以在API上投入更多的硬件,同时保持网站所需的最少硬件。很有可能你在幕后做了一些比仅仅提供HTML更激烈的工作。通过使用相同的实现,扩展变得更加容易。API本身(XML和域对象之间的封送处理以及返回)不会超过业务逻辑和处理,数据库等。

最后,通过保持它们相同,可以更轻松地将系统视为一个整体。事实上,从 HTML 开始。定义关系。如果您很难在HTML锚点和表单中表达特定的操作或用户故事,那么您可能偏离了REST。

记住,你是把事物表达为(特定关系)与其他事物的链接。这些URI甚至可能有所不同,具体取决于您是生成XML还是HTML - HTML页面可能会发布到URI some / uri / a,API可能会发布到某些/ uri / b - 这是无关紧要的,并且关注实际的URI内容是POX和RPC的黑暗路径

另一个漂亮的功能是,如果你这样做,你就不依赖于JavaScript。您已经定义了您的系统以使用基本的HTML,并且可以在JavaScript可用时“翻转” JavaScript。然后你真的在使用你的“API”(我害怕将它们称为不同的东西,但我也试图将我的回应与你的措辞联系起来)

**我将添加最后一个注释,在生成HTML时,我使用303而不是201,以便于POST-then-GET。如果启用了JS,那么您实际上是在谈论XML(或JSON),并且您又回到了201。


推荐