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服务集群也配备了完善的日志分析、性能监控、弹性伸缩、故障恢复和预警机制。

除此之外,我们还制定了前端开发规范,涵盖了工程规范和JavaScript、TypeScript、Vue.js、CSS、Jest等语言或框架的代码规范。并且在ESLint和StyleLint的基础上研发了配套的检查工具,补充了部分独有的规则。随后又融入到工程化机制、Ada工作台和持续集成流程当中,以帮助业务团队即时发现和纠正问题。

为了进一步保障用户的浏览体验,我们基于Google Chrome Lighthouse研发了Web性能监控平台,长期追踪核心产品在全国各地的性能表现。目前,基于Sentry的错误跟踪和分析平台也正在试运行中。

后记

Ada已经稳定运行了三年,也持续演进了三年,大体经历了三个阶段:

  • “打造内核”阶段,快速定型了Ada的工程化机制和服务器内核,并投入试运行;
  • “完善设施”阶段,Serverless架构的周边设施趋于完善,全面提高性能和稳定性;
  • “丰富体系”阶段,推出Ada工作台和Widget等一系列周边扩展能力,开始探索更多的可能性;

在未来,Ada还将继续迎接不断更迭的前端技术,响应不断变化的业务需求。服务器端研发能力将不再局限于BFF层,更会向开发者公开完整的全栈研发能力;Widget只是Ada涉足微前端的一个小小的尝试,我们还会引入更便于业务深度融合的微前端方案;请求函数映射机制也会从形似FaaS,进一步演进成真正意义上的FaaS……

本文从宏观层面上介绍了智联招聘的大前端架构Ada,并未过多涉及技术细节,如果大家对某个特性感兴趣,可以留言告诉我们,我们会撰写专门的文章来详细介绍。

招聘

作为智联招聘的前端架构团队,我们一直在寻找志同道合的前后端架构师和高级工程师,如果您也和我们一样热爱技术、热爱学习、热爱探索,就请加入我们吧!请将简历请发送至邮箱[email protected],或者微信搜索WindieChai沟通。

03-05 21:18