
根据 InfoQ 最新发布的《软件架构与设计趋势》报告,微服务架构在 IT 社区中已经得到广泛采用。具体来讲,报告将微服务与事件驱动架构列入了“晚期大众”的划定区间。
报告还提出了可能令人意外的事实,即微前端时至今日仍处于“早期应用”区间,未能与微服务一道成功进入“晚期大众”阶段。尽管存在几种较为成熟的微前端框架选项,但 2020 年针对 650 多名技术领导者开展的调查发现,只有 24%的受访者使用过微前端。
由此可以得出结论:虽然微服务架构在软件系统的后端应用已经非常成熟,但在前端层面却并非如此。可以肯定,对于涉及多支 DevOps 团队的大型软件系统来说,这种前端仍处于单体时代的现状无疑代表着运营和生产力层面的巨大瓶颈。
也就是说,负责补充甚至替代微前端的生态位上仍有发展空间。而当仁不让的后起之秀,正是交互式微服务。
交互式微服务
交互式微服务,也被称为 Qworum 服务,是由 Qworum 平台开创的 Web 前端模块化新概念。
多阶段 Web API
交互式微服务基于 Qworum 所定义的新型 Web API,即多阶段 Web API。这类 API 与传统 REST 或 JSON-RPC Web API 的最大区别,在于端点调用可能涉及多个请求-响应对,也就是“阶段”。

多段调用会接收调用参数并返回结果
如果调用中包含参数,Qworum 并不会真正将其发送至服务器,而是开放给浏览器内的被调用服务。如有必要,被调用的服务可以在不经 Qworum 的情况下,自行将数据发送至服务器。
基于 HTML 的用户交互
多阶段设计允许各端点在返回结果之前,先通过见面与最终用户交互。正是这一点,让微服务获得了交互能力。

交互式多段调用
服务组合
Qworum 的另一个重要特性,是端点可以要求由网络浏览器执行调用,借此调用其他端点甚至是源端点自己。其他端点可以托管在源调用端点所处的同一网站,也可以托管在网络上任意位置的其他网站。

由网络浏览器负责介导的服务组合
新的顶级网络格式
说到这里,很多朋友肯定好奇 Qworum 服务要如何执行嵌套调用,这些服务又要如何返回结果。
为此,Qworum 定义了一种新的顶级 Web 格式,名为 Qworum 脚本。支持 Qworum 的浏览器中,内置一个可以运行此类脚本的解释器。关于更多细节,我们将在“Qworum 脚本”小节中具体讨论。
用户体验
在使用体验上,基于 Qworum 的 Web 应用程序跟传统 Web 应用程序没有多大差别。
但在使用 Qworum 应用程序时,用户可能会发现浏览器显示页面中的 DNS 域可能会不时变更,但所有页面的使用感觉仍像是 Web 应用程序的一部分。这是因为 Qworum 应用程序采用分布式设计,可通过组合托管使用不同来源上的多个 Qworum 服务。

跨多个源和 DNS 域的用户流示例。
第二个区别,是浏览器的后退按钮无法在应用程序 UI 中导航至上一页面,这是因为 Qworum 应用程序和服务在执行期间不会向浏览器选项卡的历史记录添加新条目。之所以有此限制,是为了避免应用程序 UI 与 Qworum 附加至选项卡的应用状态不同步。
与微前端的区别
到这里,Qworum 和微前端的区别已经很明显了:
无限的可组合性:交互式微服务能够在执行期间调用其他端点,甚至调用自己。这意味着嵌套调用的深度将没有上限,而且无论嵌套深度如何,每次调用都会处理整页 UI。这与通常只能嵌套 1 或 2 层的微前端不同,因为随着嵌套深度的增加,分配给每个微前端的 UI 面积会越来越小。
广泛的适用性——Qworum 服务比微前端更适用于分布式应用程序,因为这类应用往往与特定 Web 应用程序(即席微前端)、前端框架(React 微前端、Angular 微前端)或者组织(各组织间的微前端通常无法互通)相绑定。

Qworum 采用的是“一切皆是微服务,包括应用程序”的 UI 范式
微前端采用的是“微前端加容器应用程序”的 UI 范式
新的网络浏览器功能
Qworum 还为 Web 环境带来了以下久经考验的计算机制:
子例程——子例程是实现软件模块化的基础机制。事实上,大多数(甚至是全部)CPU 指令集都包含负责调用子例程和从子例程返回的操作码。在高级编程语言中,子例程表现为过程、函数和方法。Qworum 的端点调用机制也是子例程的另一种高级表现形式。
对象——在面向对象编程中,对象主要作为数据的容器。在对象生命周期中,这些数据会在一组称为“方法”的子例程之间共享并保持可访问性。这个概念在 Qworum 中表现为 Qworum 对象,适用于构建购物车等有状态服务。
框架及其他
需要明确的是,Qworum 并不是那种能够轻松使用浏览器现有功能的 JavaScript 框架。相反,Qworum 自身定义了 Web 应用程序能够利用哪些新的浏览器功能。
因此,尽管也提供用于 Web 前端的官方 Qworum JavaScript 库,但该库只能算是官方 Qworum 浏览器扩展提供的功能“薄层”。Qworum 目前支持基于 Chromium 内核的浏览器,并将在不久的将来支持其他所有主流浏览器。

安装在谷歌 Chrome 上的 Qworum 官方扩展
新的浏览器内数据存储机制
Qworum 之所以无法单纯作为 JavaScript 库实现,包括之所以需要以浏览器扩展或者本机浏览器实现的形式起效,原因就是浏览器目前只面向单源 Web 应用程序。而 Qworum 所支持的是分布式 Web 应用程序,这也是由 Qworum 开创的全新通行概念。下面,我们来具体聊聊为什么当前浏览器功能不足以支持分布式应用程序:
符合标准的网络浏览器需要实施所谓同源策略,以便彼此隔离来源。其中特别要求“一个来源中的 JavaScript,将无法读取或写入属于另一来源的存储”。
同源策略会对 Qworum 形成极大限制,因为 Qworum 会话使用的是单一调用堆栈,会话中的所有 Qworum 服务都必须能够访问该调用堆栈,且无论具体来源如何。在传统编程中,执行线程也将单一调用堆栈视为标准实践。因此,Qworum 建立了一种新的、粒度更细的对象数据分离策略,而不再粗暴按源进行数据分离。新策略要求:1)属于同一 Qworum 对象的端点的所有阶段,都必须包含在同一来源中;2)如果对象 A 包含对象 B,或者 B 调用了 A 的某一端点并将其数据作为调用参数,则 Qworum 中的对象 A 可以访问另一 Qworum 对象 B 的数据。除此之外,Qworum 对象还可根据 HTTP(S)请求中 Referer 标头指示的调用者 URL,主动拒绝向调用者提供服务。这种情况意味着即使 A 包含 B,某些情况下 B 也不会向 A 提供有意义的数据。
在应用程序执行期间,Qworum 内部会使用各个浏览器选项卡的跨源调用堆栈来处理服务调用和数据。调用堆栈仅在应用程序执行期间存在,一旦应用程序退出或者最终用户关闭应用程序所在的浏览器选项卡,调用堆栈即被删除。这类似于在终端中打开 Deno 或 Node REPL 会话,所有会话对象都将在退出时被自动删除。
请注意,虽然浏览器的 History API 已经提供类似于堆栈的结构以将数据附加至浏览器选项卡,但这种数据结构并不适合管理 Qworum 的调用堆栈。这两种数据结构分别针对不同的用例,History API 的数据堆栈可通过 Web 前端直接访问,并可基于多种用途进行调整或使用;但 Qworum 调用堆栈仅独立存在。
为网站启用 Qworum
要在给定网站上运行 Qworum,需要满足以下所有条件:
在浏览器上安装 Qworum 扩展。
Qworum 扩展未被最终用户禁用。
网站的 DNS 域当前已订阅 Qworum。默认情况下,Qworum 仅针对本地开发启用。
网站可以使用 Qworum JavaScript 库来检查以上条件,并在不满足时向最终用户发出提醒。
用 Qworum 编程
Qworum 脚本
官方 Qworum 规范中定义了一种新的顶级 Web 格式,即 Qworum 脚本。这种格式以 XML 为基础,XML 是 Web 服务器除 HTML 页面以外向浏览器发送的另一种数据形式。XML 具备可样式化属性,允许对最终用户隐藏脚本内容。
Web 服务器会将 Qworum 脚本作为标准 XML 文档进行交付,内容类型为 application/xml 或 text/xml。浏览器会通过查看文档根元素的命名空间,来理解当前 XML 文档实际上是 Qworum 脚本。
Qworum 脚本的推荐文件扩展名为.qrm.xml。Qworum 还提供官方的 Visual Studio Code 扩展,可用于促进静态 Qorum 脚本的创建。此扩展目前仅可提供建议片段。也有其他功能正在规划当中,包括检查脚本语法、脚本中可能包含的任何 JSON 或语义数据的语法,以及对嵌入式数据语法的高亮显示。

正在运行的 Qworum 官方 VS Code 扩展
如何生成客户端脚本
除了以 XML 的形式从服务器发送到浏览器外,Web 前端还可以通过 Qworum JavaScript 库生成并执行 Qworum 脚本。
在库说明文档中,提到了 Qworum.eval()这种 API 方法,能够执行动态生成的 Qworum 脚本。要知道,传统上在 JavaScript 代码中使用 eval() 属于风险行为,所以很多朋友可能会怀疑使用 Qworum.eval()到底安不安全。令人欣慰的是,答案是肯定的,具体原因如下:
Qworum 脚本是用特定领域语言编写,而非图灵完备语言。Qworum 脚本只能将最终用户重新定向至新的 URL,或者关闭浏览器选项卡。
由 Qworum 脚本发起的重新定向由 HTTP GET 请求组成。换句话说,Qworum 脚本不会向服务器发送任何数据。
Qworum 脚本只能通过 Qworum 浏览器扩展(用于 Qworum 脚本的内部存储和管理)对 Qworum 调用堆栈进行间接访问。该浏览器扩展将监督脚本具体可以对调用堆栈执行哪些操作。
Qworum 脚本无法访问 Web 前端存储在客户端存储中的数据,例如 localStorage、sessionStorage 和 IndexedDB。
用户体验
大多数 Qworum 脚本在执行时,会将最终用户重新定向至新的 URL。例如,以下电子商务示例项目的链接和按钮都将执行 Qworum 脚本,在单击后即重新定向最终用户。

点击按钮即运行 Qworum 脚本,对用户执行重新定向
在某些情况下,Qworum 应用程序会关闭当前浏览器选项卡以退出。引发这种情况的原因之一,是 Qworum 脚本发现了应用程序未能捕获的故障。Qworum 应用程序会尽可能防止这类情况,以避免破坏用户体验。

Qworum 应用程序由于未捕获的错误而退出。
Qworum 应用程序示例
下面,我们将具体了解以上提到的电子商务演示项目将如何工作。请注意,Qworum 目前主要面向商业和企业级应用程序,可能不太擅长处理消费级应用程序,毕竟大多数浏览器尚未预安装 Qworum。
伪代码
我们首先会在较高层次上描述这款分布式电子商务应用的结构。其实任何面向对象的编程语言都可用于开发此任务,因为 Qworum 平台本身就是面向对象的。这里我们使用 TypeScript。请注意,此代码并是要交由 Qworum 执行。相反,其内容是要帮助读者厘清分布式应用程序的结构。
以下伪代码,描述了一套由两种 Qworum 服务类型组成的软件系统:
具有两个交互式端点 home()与 viewArticle()的电子商店,以及作为对象状态的购物车。
带有两个交互式端点 addItems()和 showCart()的购物车,以及作为对象状态的车内总价和商品列表。

伪代码:由两个服务组成的分布式 Qworum 应用程序
这里的伪代码描述了一个依赖于购物车 Qworum 服务的电子商店应用程序。按照惯例,假定所有接口方法都代表交互式 Qworum API 端口。
使用 Qworum 的 JavaScript 库
在通过 JavaScript 使用 Qworum 之前,网页首先需要导入 Qworum 库。下图所示为导入过程:

导入 Qworum JavaScript 库
大家可能注意到,在以上伪代码中,e-shop Qworum 对象包含一个购物车对象。也就是说,任何电子商店端点都可以访问购物车总金额及其商品条目列表。例如,电子商店端点可以使用 Qworum.getData()来读取购物车总价,如下所示:

由 e-shop 端点读取购物车数据
Qworum.getData() 会接收数据容器的路径作为调用参数。在以上代码中,Qworum.getData() 将查找名为“total”的数据容器,该数据容器包含在名为“shopping cart”的 Qworum 对象中,而此对象又包含在拥有当前 Qworum 端点调用的 Qworum 对象当中,具体如“@”路径元素所示。
也可以使用 Qworum.setData() 方法设置数据容器的值。

由购物车端点设置购物车状态
Qworum 脚本的生成和执行也同样简单:

由购物车的 showCart()端点结束执行
这会将最终用户重新定向至新的 URL。Web 前端可能希望在执行 Qworum 脚本之前,先检查浏览器是否在线。当前的 Qworum 会假定浏览器已经在线,但如果浏览器已离线,那么尝试启动重新定向的脚本可能会自动引发网络故障。
请注意,Qworum 端点并不清楚执行完成后会发生什么。与之对应,当前的 Web 应用程序必须将返回的 URL 传递至登录对话框页面,所以如果要在同一个页面上处理错误条件,有时可能需要传递多个返回 URL。由此可知,与当前 Web 编程方式相比,Qworum 的处理风格更类似于传统编程。
部分执行流程
当最终用户在电子商店的商品详情页面中单击“添加至购物车”按钮时,交执行以下 JavaScript 函数:

用户在电子商店应用中向购物车添加商品
关于以上代码的几点说明:
在执行过程中,调用指令不会终止脚本执行,只是将其暂时中断。当调用返回结果或引发错误时,脚本将恢复执行。
在对购物车 addItems()端点的调用完成之后,对电子商店 viewArticle()端点的调用也将同步完成。这是因为顶级序列指令将产生由 addItems()返回的结果,且当顶级指令在 Qworum 脚本中产生结果时,当前端点调用会返回该结果。
如果 goto 指令未被注释,则 viewArticle()端点调用将改为在 addItems()返回时,继续执行 goto 定义的下一阶段。
现在,让我们看看购物车在其 addItems()端点被调用时,具体执行了哪些操作。以下为实际执行的函数:

添加新商品时,购物车会更新所存储的会话状态
再次强调以下几点:
调用参数可作为本地数据供 Qworum 端点使用,通过 Qworum.getData()进行读取。
addItems()端点仅更新购物车状态,而不与最终用户交互;它会调用 showCart()端点以向最终用户显示 UI。
addItems()端点将返回与嵌套调用 showCart()相同的结果。
根据以上示例可知,Qworum 为 Web 开发者提供了一系列高效的面向对象编程原语。
企业与商业用例
虽然本文主要介绍 Qworum 平台所提供的功能和技术原理,但这里也要简要讲解如何使用 Qworum 平台即服务构建企业和商业软件。
Qworum 是一套通用平台,特别适合作为微服务架构的基础设施。在 Qworum 的帮助下,您可以轻松实现软件系统当中 UI 层的模块化。
供应商用例
适合软件供应商的 Qworum 应用场景包括:
企业套件供应商可在内部使用 Qworum,借此集成企业套件中的各类元素。
在企业套件中集成同类最佳的各种应用程序。
使用 Qworum 将多种彼此独立的同类最佳应用程序集成起来。
部署用例
Qworum 适用于各种部署和集成类型,包括本地、云端,也涵盖定制化软件、现成商业软件乃至 SaaS。
项目管理应用程序示例
这里以人力资源应用、资源预订应用和在线协作应用等功能集成构建的项目管理应用程序为例。本文不提供具体工作代码,但此类系统的为代码可能如下所示:

分布式项目管理应用程序的高级描述。
知识产权
多阶段 Web API 的概念最早出现在名为“服务组合的方法和系统”的实用专利当中。尽管该专利已经过期,但为了防止平台出现碎片化倾向、避免因碎片化对开发者的生产力带来负面影响,我们还是决定为 Qworum 的新功能申请相应专利。
Qworum 是一家商业机构,但也欢迎广泛的 IT 社区向 Qworum Google 官方群组做贡献,或者针对 Qworum 规范提交反馈、建议和请求。
总结
大家应该还记得,当初万维网的设计方向其实是内容平台、而非具体应用。所以考虑到网络体系的发展历程以及客观存在的改进空间,Qworum 出于更好支持 Web 应用程序而做出的重大更新自然也在情理之中。
总而言之,Qworum 为 Web 平台带来了高级浏览器功能,有助于提高工作效率、并为使用微服务架构的开发运营团队提供用例支持。期待大家多多提供宝贵意见!
原文链接:
https://www.infoq.com/articles/qworum-modular-ui-architecture/
评论 1 条评论