Web服务应该与网站分开吗?

时间:2011-11-16 19:40:18

标签: java web-services spring-mvc

我正在构建一个网站,并且还希望构建一个REST Web服务来访问许多相同的功能(使用google app engine和spring mvc3),而且我不确定如何集成/分离的最佳实践这两部分应该是。

例如,如果我想查看资源,我可以提供表格中的网址:

{resourcetype}\{resourceid}

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

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

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

4 个答案:

答案 0 :(得分:15)

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

首先,您将自己的网址与API网址分离,以便每个网址都可以独立更改。例如,您可能需要向API发布向后不兼容的更改,在这种情况下,您可以创建/ v2 /目录。同时,您可能需要网站上的/ about页面,但不需要一个API。

通过使用不同的网址,您可以简化实施。现在,每个方法都不必确定它是否面向JSON / XML或HTML。即使你有像Spring这样繁重的框架,这也是如此。对于当前用户,您仍然为网站做了额外的事情。

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

您可以轻松地分离逻辑以进行身份​​验证。有了网站,您需要一个登录页面和cookie。使用API​​,这些不是必需的,但是额外的身份验证标头(例如,HMAC + sha256签名)。

最后,通过将站点与API分离,您​​可以满足不同的扩展需求。如果您的API受到严重打击而网站不受影响,您可以在API中投入更多硬件,同时保持网站所需的最低限度。


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

首先,在MVC的说法中,您对该模型有一个模型和两个不同的视图。这是MVC的重点,因此视图和模型不会捆绑在一起。获取特定资源的代码在两个客户端中都是相同的,因此您可以编写模型,使只有一行代码从数据库或其来源获取该资源。简而言之,您的模型是一个易于使用的库,有两个客户端。

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

答案 1 :(得分:8)

我不同意Michael的回答,并将其作为我自己的基础:

“将您的网址与您的网址分离,以便每个网址都可以独立更改。”是面对REST吐。 Cool URIs don't change。不要担心自己更改网址。不要使用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,我使用一些特定的链接(由Relation定义)作为“action”属性。)

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

“最后,通过将网站与API分离,您​​可以满足不同的扩展需求。如果您的API受到严重影响而不是网站,您可以在API上投入更多硬件,同时保持网站所需的最低限度。 “ - 你的缩放不应该取决于谁在这里使用什么。很可能你在幕后做了一些比仅提供HTML更强烈的工作。通过使用相同的实现,缩放更容易。 API本身(XML和域对象之间以及后面的编组)不会超出业务逻辑和处理,数据库等。

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

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

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

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

答案 2 :(得分:7)

为了支持迈克尔,与道格相反,你应该将它们分开。

原来,浏览器的基本形式并不是特别好的REST客户端。由于缺乏对HTTP的完全支持,糟糕的身份验证支持,以及对媒体类型的弱支持,浏览器实际上非常有限。如果您仅限于简单地使用内容,并且该内容恰好是HTML,那么浏览器就可以了,但是超出这个范围并且API会受到浏览器中不良支持的影响。

JavaScript可以提高浏览器的功能,并使其成为更好的REST公民,但是在浏览器中很少有东西比静态HTML页面更好。便携,高性能,可扩展到不同设备,具有一些CSS乐趣。每个人都喜欢静态页面,特别是那些不会托管数以万计的图像,而不是其他慢速提供商的页面。单击,BANG,快速显示的快速滚动页面。

由于浏览器是一个悲伤的公民,因此您不应将API限制为其弱功能。通过分离它们,您可以在HTML + JS,Flash或Java,Obj-C for iOS或Android等中编写一个漂亮,丰富,动画,有弹性,令人兴奋的界面。

您还可以在PHP中编写一个漂亮的前端,托管在您的服务器上,并将结果推送到浏览器客户端。 PHP应用程序根本不必是REST,它可以只是一个通用的Web应用程序,在域中工作和Web应用程序的约束(有状态会话,非语义标记等等)。浏览器与PHP对话,PHP与您的REST服务对话。 PHP应用程序允许您将浏览器的需求与REST服务的语义分开。

即使使用纯HTML,您也可以编写更多RESTful HTML应用。他们只是变得非常糟糕的应用程序,人们不喜欢使用。

显然,通用Web应用程序和REST服务之间存在很多可能的重叠,但重叠不是平等,而且它们是不同的。 HTTP!= REST,使用HTTP并不意味着您正在使用REST。 HTTP非常适合REST应用程序,但您当然可以以非RESTful方式使用HTTP。人们整天都这样做。

因此,如果您想将REST用作服务层,那么就这样做。为REST而利用REST,并构建您的服务。然后,开始处理利用该服务的客户端和接口。不要让您的初始客户选择为REST服务本身着色。专注于功能的使用案例,然后围绕它建立您的客户。

随着每个组件的需求发生变化,它们可以根据需要与彼此一致或彼此分开地增长。您不必为了浏览器所需的更改而惩罚移动应用程序,反之亦然。让每件作品都是他们自己的主人。

附录:

山姆 -

提供混合方法没有错,其中一些请求由REST服务层直接提供,而其他请求则通过服务器端代理处理。只要语义相同,它就不重要了。您的REST服务当然不在乎。但是,如果REST服务返回特定于&#34; raw&#34;的链接rels,则可能会成为问题。 REST服务而不是混合服务。现在你有一个翻译表示法等问题。我的基本观点是不要让浏览器限制摇晃你的REST API,你可以使用一个单独的外观并让浏览器影响它。

这是一个逻辑上的分离,无论这些都表现在URL模式中,我都没有意见。这更像是一个开发/维护/部署调用。我发现可以在物理上表现出来的逻辑分离在清晰度和理解方面有一些好处,但那就是我。

道格 -

原始HTML用户体验非常糟糕。如果不是,那么整个行业就不会使浏览器用户应用程序体验变得非常糟糕。当然它可以是功能性的,并且使用HTML是REST应用程序的一种出色的媒体类型,因为浏览器的工具和使得使用界面,查看工件,在可能的情况下与服务交互更容易。但是,您不必围绕调试器设计服务API,而原始浏览器是完全利用HTTP的不完整工具。作为通过XHR的JS的主持人,它变得更有能力,但现在我们正在谈论富裕的客户&#34;而不仅仅是浏览器中的直接HTML。

虽然你可以为delete等制作服务POST外观,但在你的例子中,你这样做的唯一原因是因为浏览器的限制,而不是为了API本身。如果有任何问题,它会使API变得混乱和复杂化。 &#34;不止一种方法。&#34;

现在,显然你可以通过POST隧道传输一切,只是因为你想通过POST隧道传输所有内容。但是通过以这种方式隐藏事物,您可以绕过协议的其他方面。如果你将foo / 1发送到/ deleteFoos,你将无法利用缓存之类的东西。 foo / 1上的DELETE会使看到该操作的任何缓存无效,但POST会直接滑过,留下旧的,现在已删除的资源。

因此,即使本机浏览器选择不使用它们,协议中还有其他动词而不是POST和GET的原因。

答案 3 :(得分:0)

我认为您有理由要求使用javascript来使用ajax技术来执行您在问题的第三段中建议的内容。

此外,澄清一下,无论客户端使用201响应的Location头来指示新创建的资源的规范URI。对于使用它的客户,可以通过javascript检查这些内容。

关于'dumber'客户端(比如:禁用js的浏览器),让他们进行重定向的一种有点丑陋的方法是在POST的返回资源的html表示头部分中进行html元刷新。只要您使用Location标头,响应主体就可以简单地描述您的新资源。