Ada 是智联招聘自主研发的演进式大前端架构。于 2017 年正式投入使用后,又经过三年持续演进,全面覆盖了从研发到运维的各个方面,具备跨技术栈工程化体系、交互式图形界面开发工具、自动化发布流程、Serverless 运行时和完善的监控预警设施。目前已经支撑集团内数百个工程,在线 URL 数量多达数千,每日承载请求量逾十亿次。
本文将摘取 Ada 的一些关键特性,向大家介绍 Ada 的演进成果和设计思想。
可演进的工程化机制
“可演进”是 Ada 最核心的设计思想。
Ada 的最初版本实际上是它的内核,投入使用后便一直保持每两至三周一个版本的演进速度,不断地巩固内核,完善周边设施,同时开放更多研发能力。我们希望所有工程都能享受到最新版本的特性,不愿意看到工程版本随着时间推移变得碎片化。
考虑到 Webpack 的灵活性和复杂性会不可避免地助长碎片化,我们决定将其隐藏到 Ada 内部,由 Ada 来承担起统一工程化机制的责任。
Ada 规范了工程的目录结构,将指定目录下的次级目录作为 Webpack Entry 处理,实现了对 SPA 和 MPA 的同时支持,更容易支撑巨量级的复杂视图。
同时,Ada 还统一处理了 Webpack Loader 及插件的使用方式、CDN 地址、Code Split、SourceMap、代码压缩等构建细节,并且自动处理了不同部署环境之间的差异,标准化了工程的构建输出形式。
针对工程之间可能存在的合理的差异性配置,比如域名、根路径和语言处理器(Webpack Loader)等等,Ada 还向业务团队提供了一个更加精简的工程配置文件。
通过工程规范和工程配置文件,我们把 Ada 塑造成了一名“Webpack 配置工程师”,它会处理好所有涉及到 Webpack 的工作,业务团队无需关心此类细节。我们也因此对工程化机制有了更强的治理和演进能力,能够在不影响业务团队的情况下进行迭代(比如调整逻辑、修复问题、升级 Webpack 版本、甚至更换到其他打包工具等等)。
支持多框架
为了更好地支持业务特有的技术诉求,以及应对不断涌现的新框架和新技术,Ada 从一开始就将多框架支持能力当作了一个重要的设计目标。
依托于统一的工程化机制,Ada 可以根据各种框架的特点针对性地调整 Webpack 配置,形成新的脚手架。所有脚手架都延用了一致的工程规范和工程配置文件,最大程度上保证了一致的开发体验,减少了框架的切换成本。
我们选择 Vue.js 作为公司的主要前端框架,并为其研发了专门的脚手架。Vue.js 脚手架保留了 Vue.js 在研发效率方面的优点,允许开发者配置多种 CSS 处理器,并对服务器端渲染提供了良好的支持。
随后,Ada 又提供了 Weex 脚手架来支持移动端快速开发,帮助业务团队将一套代码同时运行在浏览器、iOS 和 Andriod 中。
针对需要支持旧版 IE 浏览器的业务,我们选择了 MVVM 模式的鼻祖框架 Knockout.js,并将 Vue.js 广受赞誉的的单文件组件机制引入到 Knockout.js 脚手架中,为开发者带来了和 Vue.js 脚手架一样的开发体验。
此外,Ada 还提供了用于开发 Web API 的 Node.js 脚手架,并逐步为它增加了 TypeScript 支持和 GraphQL 研发能力。
“可演进”的 Ada 工程化机制为新框架预留了充足的扩展空间,也让我们更容易跟进框架的版本更迭,持续为业务团队开放框架的完整能力。
服务器端研发能力
Ada 基于 Koa 研发了 Web 服务器,并开放了服务器端研发能力,赋予前端工程师更全面的掌控力。不但可以在 UI 层面执行权限校验、重定向和服务器端渲染(SSR)等操作,还能够通过研发 Web API 来实现 BFF 层(Backend for Frontend)。完整的服务器端研发能力能将前后端的接触面(或摩擦面)从复杂的视图层面转移到相对简单可控的 BFF 层面,实现真正意义上的前后端分离,继而通过并行开发来最大程度提高开发效率。
为了进一步降低服务器端研发难度,Ada 在脚手架目录结构规范的基础上,进一步规范了路由函数的声明方式,形成了从 HTTP 请求到函数的映射关系。请求函数是一个异步函数,Ada 会向它传递一个上下文对象。这是一个经过了悉心封装的对象,它包含了当前 Request 的所有信息,提供了全面控制 Response 的能力,并且统一了 Web API 和 SSR 的 API。
借助请求函数映射机制和自定义上下文对象,Ada 向开发者提供了一种更加简单直接的、面向请求的开发方式,同时隐藏了 Koa 和 Web 服务器的技术细节。这种设计使得业务团队可以更加专注于产品迭代,架构团队也能在业务团队无感知的情况下进行日常维护和持续演进(比如调整逻辑、扩充能力、升级 Node.js 版本、甚至更换到其他 Web 服务器框架等等)。
Serverless 架构
在降低服务器端开发门槛的同时,我们也希望能够降低服务器的运维和治理难度,让前端工程师不必分心于诸如操作系统、基础服务、网络、性能、容量、可用性、稳定性、安全性等运维细节,从而将更多的精力投入到业务和专业技能上。基于这样的考虑,我们引入了 Serverless 架构。
我们借助容器技术搭建了服务集群,将 Ada 演进成为一个更加通用的运行时,除了函数发现以及通过执行函数来响应 URL 请求之外,还对运行时自身提供了全方位的保障。Ada 服务器有完整的请求生命周期追踪机制和日志 API,能够自动识别和阻断恶意请求,还能从常见的 Node.js 故障中自动恢复。此外,服务集群也具备完善的安全防御和性能监控设施,并实现了容量弹性伸缩,在节约成本的同时也能更好地应对流量波动。
如此一来,服务便从工程中脱离出来,成为 Serverless 服务集群的一员,继而通过发布流程来将服务和工程连接起来。发布流程也运行在云端,分为部署和上线两个阶段。部署阶段仅仅执行文件构建、上传和注册,不会对线上版本产生任何影响。部署完成后,就可以在发布中心上线具体的 URL 版本,并且可以随时回滚至历史版本。无论发布还是回滚,都会即时生效。
URL 粒度的发布方式更加契合前端业务的迭代习惯,更加灵活,与单体应用的整体发布方式相比也更加安全可控。工程作为一种代码组织形式,不再承担服务的责任,可以随时根据需要进行合并和拆分,也能更好地适应虚拟团队这样的组织形态。
工作台
和许多框架一样,Ada 早期也提供了一个命令行工具来辅助开发。命令行工具的局限性非常明显,呈现形式和交互形式都过于单一。随着 Ada 的逐步采用,日常开发过程中产生的信息和所涉及的操作都愈发繁杂。我们需要一个更具表现力的工具来进一步提高工作效率,便基于 Electron 研发了 Ada 工作台。
Ada 工作台并不是命令行功能的简单复刻,而是对前端图形界面开发工具的大胆想象和重新定义。我们为 Ada 工作台添加了丰富的功能,全面覆盖了前端工作流程中的开发、调试、发布等环节,使它成为真正的一站式前端开发工具。
我们在 Ada 工作台中引入了 URL 级别的按需构建。开发者选择 URL 之后,Ada 工作台就会自动启动多个构建器来执行构建,同时以图例的形式展现构建情况。构建中出现的任何问题,比如未找到引用或者未通过开发规范检查,都可以直观地看到提示,点击提示则能浏览更详细的信息。按需构建既提升了构建速度,也在一定程度上有效地避免了 Webpack 在构建大型工程时可能出现地各种问题。
除了手工启动构建之外,Ada 工作台提供了一种更加便利的方式——“访问即构建”,通过监听对 URL 的访问,自动启动按需构建,并在构建完成后主动刷新页面。“访问即构建”通过自然的本机调试行为来触发构建,免去了手工逐个选择 URL 的繁琐操作,很快就成为了开发者的首选构建方式。
虽然服务器端代码最终运行于 Serverless 环境,但并不意味着开发阶段只能远程调试,为了便于调试,Ada 工作台内置了 Ada 服务器的一个开发版本,该版本仅对本机开发流程进行了适配和功能缩减,其余特性和 Serverless 版本保持高度一致,诸如端口冲突、环境差异等等困扰开发者的效率障碍在很大程度上都被消除了。
Ada 工作台还提供了一个交互式的日志查看器,来帮助开发者浏览本机开发时输出的日志。所有日志都会以非常简约的形式呈现,可以通过点击来浏览明细,同时也提供了关键字搜索和日志级别过滤等功能,以便开发者能快速找到所关心的调试信息。
发布流程也被无缝嵌入到 Ada 工作台中,并且得到了进一步增强,能够方便地执行 URL 级别的按需发布。
目前,Ada 工作台已经成为公司前端技术体系的重要基础设施。前端技术领域还在不断涌现出各种新的概念,而 Ada 工作台的想象空间依旧很大,这也让我们对它未来能发挥的作用更加期待。
移动端研发能力
我们选择了 Weex 作为移动端的快速研发框架,帮助业务团队使用熟悉的 Vue.js 语法开发可以同时运行于浏览器、iOS 和 Andriod 中的应用。
Weex 脚手架遵循了 Ada 的工程化机制,可以享受 Ada 工作台提供的开发和调试便利。此外,Ada 工作台还以插件的形式内置了 Weex 真机调试工具,以便在 App 内进行调试。
在开发模式上,我们最大程度保留了 Web 的特征,为前端工程师带来更加熟悉的开发体验,Web 风格的 URL 路由方式也在 Native 内核中得到了支持。Native 内核向 Weex 提供了全方位的支持,包括路由、缓存、视图组件、互操作 API 等等。针对历史遗留的 Native 平台差异问题,则通过我们研发的 mobile-js-bridge 来将它们封装成一致的 API。
此外,我们为 Weex 也提供了 URL 粒度的发布能力,能够独立于 App 的版本进行发布,极大地提高了移动端的迭代速度和问题响应速度。
Ada 充分发挥了 Weex 在快速迭代方面的优势,广泛地应用于公司的各个移动端产品中,先后帮助业务团队答应了多场快速交付战役。
能力扩充
Ada 除了支持开发 Web 页面,还支持开发一种特殊的视图——Widget。作为微前端架构的一种实现,Widget 运行在宿主页面中,可以独立开发和发布。其设计目标是解耦代码、流程和团队,帮助业务团队进行跨技术栈、跨产品以及跨团队的功能复用。比如公司所有产品线都需要使用统一的登陆注册 Widget,后者由平台团队来维护,在保证兼容性的前提下就可以自行迭代演进,而不需要各产品线逐版本配合发布。Widget SDK 负责维护 Widget 的生命周期,并提供了类似于 Web Worker 的通信机制,从而实现 Widget 和宿主页面在技术框架、代码逻辑和发布流程上的完全独立。
Widget 是一种在客户端复用能力的机制,在服务器端,Ada 提供了请求上下文扩展来实现能力复用。请求上下文扩展是一组可以独立开发和发布的函数,发布之后的函数会附加到请求上下文,供特定范围的请求函数调用。借助请求上下文扩展,业务团队可以更方便地复用诸如用户认证和授权之类的服务器端公用能力。
此外,Ada 服务器还内置了一些常用的第三方模块的多个版本,比如 vue-server-renderer、axios 和 pg 等等。开发者可以通过专门的公共模块 API 来引用这些公共模块的制定版本。由 Ada 服务器统一提供的公共模块一方面提升了工程的构建速度,减小了输出体积,另一方面也规避了 Webpack 无法处理 Node.js Native 的问题。
在对 GraphQL 进行了大量调研和实践之后,我们决定通过工具包的形式提供 GraphQL 开发能力。GraphQL 工具包同时支持 graphql-js 和 Apollo GraphQL 两种实现,并且可以将 Schema 转化为 Ada 请求函数,从而在 Ada 服务器中执行。GraphQL 工具包会识别 Schema 中的异步 Resolver,并将它们注册到 Ada Server 的性能监控和请求跟踪机制中,为业务团队在合并了多个操作的请求中定位问题提供便利。
得益于 Ada 的“可演进性”,我们能够更加稳健地响应业务诉求,持续不断地将技术洞察转换成新的能力,以更加“Ada”的形式提供给业务团队,上述能力扩展就是其中的典型示例。
质量保障
我们采取了多种技术手段来保障 Ada 核心代码的质量和 Serverless 服务集群的稳定性。
Ada 核心代码遵循了相当严格的开发规范,并通过数千个单元测试用例 100% 覆盖了全部代码和执行路径。针对单元测试可能出现的“非有意覆盖”情况,我们特别设计了“混沌模式”,通过随机删除特定的代码来检验测试用例的全面性。
为了确保 Ada 服务器的变更不会破坏 API 的向下兼容性,我们在集成测试阶段将 Ada 的测试版本部署到一组测试容器中,并请求预先发布的测试 URL 来逐个进行检查 API 的功能是否正常。Serverless 服务集群也配备了完善的日志分析、性能监控、弹性伸缩、故障恢复和预警机制。
后记
Ada 已经稳定运行了三年,也持续演进了三年,大体经历了三个阶段:
“打造内核”阶段,快速定型了 Ada 的工程化机制和服务器内核,并投入试运行;
“完善设施”阶段,Serverless 架构的周边设施趋于完善,全面提高性能和稳定性;
“丰富体系”阶段,推出 Ada 工作台和 Widget 等一系列周边扩展能力,开始探索更多的可能性。
在未来,Ada 还将继续迎接不断更迭的前端技术,响应不断变化的业务需求。服务器端研发能力将不再局限于 BFF 层,更会向开发者公开完整的全栈研发能力;Widget 只是 Ada 涉足微前端的一个小小的尝试,我们还会引入更便于业务深度融合的微前端方案;请求函数映射机制也会从形似 FaaS,进一步演进成真正意义上的 FaaS……
本文从宏观层面上介绍了智联招聘的大前端架构 Ada,并未过多涉及技术细节,如果大家对某个特性感兴趣,可以留言告诉我们,我们会撰写专门的文章来详细介绍。
评论