2019 年 7 月

第 34 卷,第 7 期

[孜孜不倦的程序员]

裸编码:裸网络

作者 Ted Neward | 2019 年 7 月

Ted Neward欢迎回来,使用 NOF 的开发人员们。上次,我探讨了 NakedObjects 操作,它将给定业务对象的业务逻辑收集到 NOF 框架为用户提供的方法中,以供直接操作和使用。在某种程度上,我想继续该体验,因为在现有的 NOF Angular 客户端中,调用客户端的某个操作最终会以某种方式在服务器上结束,它可以从中向数据库写入更改。由于某种原因,将在某个位置进行一次从客户端到服务器的网络遍历,了解这一点很重要,以免它因大肆宣传而被掩盖。

冒着又多件衣服的风险(可选双关语),你准备好使用裸网络了吗?

REST 探究

要理解 NOF Restful API 网络标准/协议,首先需要掌握一些关键概念。我想就 REST 进行讨论,但不是像架构师和市场宣传机那样通常以漫不经心的方式引出 REST。我需要回到它的起源地并审查源材料。

早在 2000 年,Roy Fielding 发表了博士论文,作为 REST 的起源地,一个新的网络运动诞生了,不过,公平地说,更准确的说法是对现有网络方法进行了剖析和描述。Fielding 的论文是对分布式系统设计的精妙研究,强烈建议任何想要了解如何更有效地构建分布式系统的人员阅读。大多数人关心的部分是文档的第 5 章,标题为“表述性状态转移 (REST)”。(整篇论文可在 bit.ly/1NbHM8Y 上找到,或者,如果愿意,可以直接跳到第 5 章 bit.ly/1eTY8AI。)

Fielding 博士论文的新读者会发现第 5 章特别有趣(尽管老实讲,通篇文档都很有趣,绝对值得一读),因为 Fielding 从零开始小心谨慎地“构建了”REST 架构风格(他对此处提及的术语的处理相当慎重),从他所谓的 NULL 风格开始:“NULL 风格描述了一个组件之间没有明显边界的系统。” 他慢慢地研究了一系列其他风格,指出在每种情况下,都有一个对构建万维网非常有用的风格元素,这就是 REST 的案例研究。

文档中应注意的关键区域是 5.1.5 节的“统一接口”。 我在此引用整段话:

REST 架构风格与其他基于网络的风格的主要区别在于它强调组件之间的统一接口。将软件工程的通用性原则应用于组件接口,简化了整个系统的体系结构,并提高了交互的可视性。实现与其提供的服务相分离,这将推动独立可演化性。然而,这样做的代价是,统一接口会降低效率,因为信息是以标准化形式传输的,而不是以特定于应用程序需求的形式。REST 接口设计旨在高效地进行大粒度超媒体数据传输,针对 Web 的常见情况进行优化,但对于其他形式的架构交互,生成的接口并非最佳。为获得统一接口,需要多个架构约束来指导组件行为。REST 由四个接口约束定义:资源标识;通过表示形式操作资源;自描述信息;以及作为应用程序状态引擎的超媒体。

这里有大量的概念需要解释,但最后一句话是理解 NOF Restful API 的关键:“作为应用程序状态引擎的超媒体”意味着网络交互的整个状态存储在一个超媒体文档中,在客户端和服务器之间共享。在传统 Web 场景中,超媒体文档是 HTML,该文档被发送回客户端,服务器上没有客户端标识的进一步痕迹,转而允许服务器相对于客户端完全无状态。(顺便说一下,正是由于无状态的原因,Fielding 选择忽略服务器上 cookie 作为客户端标识符的概念;事实上,Fielding 和 HTTP 标准都完全忽略 cookie。)

在实践中,这意味着,如果网络交互将自身描述为“RESTful”,它需要遵守此“统一接口”原则,并捕获超媒体中的所有状态,也就是说,采用包含客户端允许的“后续步骤”的文档格式,一个在客户端和服务器之间交换的文档。

(顺便说一下,请注意 Fielding 已明确表示,REST 并不能始终确保其出众性:“REST 接口设计旨在高效地进行大粒度超媒体数据传输,…但对于其他形式的架构交互,[生成]的接口并非最佳。” 如果你在下一个项目中使用其他软件架构风格而不是 REST,并不会中伤 Fielding。)

Restful API

Fielding 论文中所述的 NOF Restful API 介绍了一个基于 HTTP 的协议,该协议将包含 REST 原则及其所有优点和缺点。具体而言,Restful API 限制了所有围绕作为应用程序状态引擎的超媒体的通信(REST 实践者称之为“HATEOAS”,我的个人博客作者将其评为“有史以来最糟糕的缩略词”),这样,来自 NakedObjects 客户端的每次交换都在一个完整的超媒体请求中完成,并生成一个完整的超媒体文档结果。当然,最简单的方法是查看它的实际运行情况,因此,如果 NOF 会议项目还没有在你的计算机上运行,则启动它。然而,这一次,不是将浏览器指向 localhost:5001(Angular 项目将在这里下载至浏览器),而是将它指向 localhost:5000,这是服务器侦听来自浏览器的 Restful API 请求的端口。

初始请求 http://localhost:5000 生成基于 JSON 的结果,如图 1 所示。

图 1 初始超媒体请求

{
  "links":[
  {
    "rel":"self",
    "method":"GET",
    "type":"application/json;
      profile=\"urn:org.restfulobjects:repr-types/homepage\"; charset=utf-8",
    "href":"http://localhost:5000/"
  },
  {
    "rel":"urn:org.restfulobjects:rels/user",
    "method":"GET",
    "type":"application/json; profile=\"urn:org.restfulobjects:repr-types/user\";
      charset=utf-8",
    "href":"http://localhost:5000/user"
  },
  {
    "rel":"urn:org.restfulobjects:rels/services",
    "method":"GET",
    "type":"application/json; profile=\"urn:org.restfulobjects:repr-types/list\";
      charset=utf-8; x-ro-element-type=\"System.Object\"",
    "href":"http://localhost:5000/services"
  },
  {
    "rel":"urn:org.restfulobjects:rels/menus",
    "method":"GET",
    "type":"application/json; profile=\"urn:org.restfulobjects:repr-types/list\";
      charset=utf-8; x-ro-element-type=\"System.Object\"",
    "href":http://localhost:5000/menus
  },
  {
    "rel":"urn:org.restfulobjects:rels/version",
    "method":"GET",
    "type":"application/json; profile=\"urn:org.restfulobjects:repr-types/version\";
      charset=utf-8",
    "href":http://localhost:5000/version
  }],
  "extensions":{}
}

那是一大堆无用数据,至少在解包前是这样。但请考虑已经显而易见的结构:它是一个 JSON 文档,由一个根对象组成,根对象有两个字段,“links”和“extensions”。 后者为空,但前者是一组“rel”/“method”/“type”/“href”字段,在本例中,它们中的五个都是 GET 请求,“href”字段提供…嗯,它听起来就像一个超链接引用。

让我们尝试一个更简单的超链接,即最后列出的一个超链接,它在链接中有“version”一词。将其弹出到浏览器栏中会得到图 2**** 所示的结果。

图 2 简单的超链接

{
  "specVersion":"1.1",
  "implVersion":"8.1.1\r\n",
  "links":[
    {
      "rel":"self",
      "method":"GET",
      "type":"application/json;
        profile=\"urn:org.restfulobjects:repr-types/version\";
        charset=utf-8",
      "href":http://localhost:5000/version
    },
    {
      "rel":"up",
      "method":"GET",
      "type":"application/json;
        profile=\"urn:org.restfulobjects:repr-types/homepage\";
        charset=utf-8",
      "href":http://localhost:5000/
    }
  ],
  "extensions":{},
  "optionalCapabilities":{
    "protoPersistentObjects":"yes",
    "deleteObjects":"no",
    "validateOnly":"yes",
    "domainModel":"simple",
    "blobsClobs":"attachments",
    "inlinedMemberRepresentations":"yes"
  }
}

毫无疑问,两者大同小异:这一次,顶级对象具有针对 Restful 对象规范版本的“specVersion”和“implVersion”,以及前面看到的“links”数组(以及一些与当前讨论无关的其他字段)。

另一方面,如果导航到第一个请求中提到的“用户”链接 (http://localhost:5000/user),将获得图 3 所示的内容。

图 3 来自用户链接的结果

{
  "links":[
    {
      "rel":"self",
      "method":"GET",
      "type":"application/json; profile=\"urn:org.restfulobjects:repr-types/user\";
        charset=utf-8",
      "href":"http://localhost:5000/user"
    },
    {
      "rel":"up",
      "method":"GET",
      "type":"application/json;
        profile=\"urn:org.restfulobjects:repr-types/homepage\";
        charset=utf-8",
      "href":"http://localhost:5000/"
    }
  ],
  "extensions":{},
  "userName":"",
  "roles":[]
}

这提供了现在所熟悉的“links”数组,以及“userName”和“roles”。

要获得 Angular 应用程序中任何感兴趣的对象(比如我的扬声器),我可以用目视扫描主页外的菜单。对于 Restful API,这意味着我需要检查主页上的“菜单”链接,查看选项。在返回的 JSON 中(为了避免在未格式化的 JSON 输出上浪费过多的列长,此处将省略),我得到一个“value”数组,它由几个对象组成,每个对象都有一个“title”(菜单项文本),比如“Speaker”和“Talks”,以及一个对应的“href”,它分别提供到 SpeakerRepository 和 TalkRepository 的链接。毫无疑问,这些链接依次提供转向这些服务(每个服务都在自己的子菜单中)等所提供的操作的链接。

简而言之,Restful API 提供与 Fielding 在 REST 一章中最初描述的完全相同的内容:作为应用程序状态引擎的超媒体(在本例中是以包含嵌入式链接的 JSON 文档的形式)。在不构建自己的浏览器的情况下,这已经非常接近 REST。(仔细想想,从技术上讲,这就是 Naked Objects 客户端:一个为与域对象交互而不是与 HTML 交互而构建的浏览器。)

顺便说一下,Restful API 并不是第一次讨论此类交互;由 Microsoft 首创的 OData 规范是为此做出的另一项努力,遗憾的是,它在世界上的非 Microsoft 平台中触礁了。但在此之前,许多 digiterati 就如何使用 JSON 或 RSS XML 来实现这种类型的交互进行了讨论。

这种方法的一个明显好处在于测试:要测试特定终结点,如果整个服务无状态,则只需提交正确的带属性和参数化的请求,服务器应使用正确的响应进行响应。在某些情况下,特别是对于那些必须保留在授权墙后面的服务,这可能需要额外的请求/响应周期,但与 RPC 风格的 HTTP API 通常命令的深度嵌套的一系列调用相比,它显然仍然领先。

总结

Naked Objects Framework 中仍有许多有趣的概念,但这仅适用于本专栏所涉及的任何主题,现在可以继续前进了。请记住,本系列文章的重点并不是让你在自己的项目中使用 NOF(不过,如果情况有利的话,我由衷地赞成这个想法),但还需要考虑如何在你自己的自定义项目中使用这些概念。正如你已所看到的,Naked Objects 是域对象概念的最终表达,如果项目已经达到通用语言状态,并且能够清楚地标识域对象,但随着业务的增长和发展,预计随着时间的推移会出现一定程度的波动和变化,构建一个用于显示和操作这些对象的微型 NOF 框架可能会很有用。同样,当在运行时通过检查对象动态生成 UI 时,几乎不可能有不一致的用户界面。此外,不要启动版本控制,拥有一个知道如何根据运行时特性来显示和操作对象的客户端就可以很好地证明,不需要在域对象的属性或操作每次响应业务需求发生更改时都推出新的客户端版本。

不管是喜欢还是排斥,Naked Objects 都是一款触手可及的强大工具,每个开发人员都应花些时间去探索它,然后再继续前进。

说到这,我们也该继续前进了。现在...祝编码愉快!


Ted Neward 是本部位于西雅图的 Polytechnology 公司的顾问、讲师和导师。他写过大量文章,独自撰写并与人合著过十几本书,并在世界各地发表演讲。可通过 ted@tedneward.com 与他联系,也可阅读他的博客 blogs.tedneward.com

衷心感谢以下技术专家对本文的审阅:Richard Pawson


在 MSDN 杂志论坛讨论这篇文章