私有Node Package的持续交付流程实践

摘要:软件是一门艺术,交付是一门学问,持续交付(Continuous Delivery,CD)则是一种对卓越的追求。CD是一种软件工程方法,项目团队通过这种方法保持在较短周期内生产出有价值的产品,并且能够保证在任何时间发布可靠的软件。CD正在业内获得越来越多的关注与支持,CD的提倡者认为它能够让公司各个组织变得更加迅速和高效,并且可以快速可靠地为市场带来服务层的使用改善。但真正的去实现CD可能会是一个很有挑战性的过程,本文就一个上传FTP前端静态资源的Node Package(ne-delivery)为实例,以个人对CD的理解摸索实践Node Package以及前端应用持续交付流程,并且我们讨论的重点是后者。


研究背景: WEB应用的需求和多终端化推动了前端技术的进步。前、后端分离的架构得到了越来越多的认可,越来越多的团队正在尝试、推广这种架构。前后端的代码在组织形式、调用结构等方面分离的结果就是前端团队和后端团队越来越独立,与其说前后端团队间的相互独立,不如说是前端团队的迅速壮大。前端已经发展到组件开发、框架开发、应用开发以及UX设计等团队角色的独立组织。
前端的独立也意味着承受的需求增加。坦白说,在我所接触的以互联网为商业模式的企业中,大部分前端项目还停留的手工打包发布,并且很少的有在进行自动化的测试,这样必然缺乏了管理并且非常容易出错。究其原因,我想主要是因为构建一个可信赖,适合企业现有环境的持续交付流程并不是那么的简单。但并不代表CD的流程优势没有被越来越多的个人和企业组织所关注,这种短周期,小粒度并且全程自动化的交付方式,要求整个过程中开发团队、运维团队等的相互协作,打破了沟通的时空障碍,反馈的快速收集,从而改善软件的质量并伴随着成本的节约。

基于对以上的认识,我以开发一个私有Node Package(ne-delivery)为例,实践Node Package的CD流程。下文就如何实现进行展开:

  • ne-delivery的设计与实现
  • 使用svn进行版本控制
  • 使用Jest进行自动化测试
  • 使用Jenkins进行持续集成
  • 使用NPM发布到Sinopia

让CD在前端应用中得以实现,不止这一种的方法,场景不同方案不同,但对于该项目已经足够,并且也覆盖到了CD中提倡的主要实现环节。


引言:通常团队通过版本控制工具进行开发协作,所有的代码会在持续集成环境中进行构建、自动化测试、质量反馈等。持续交付则更先进一步,它将环境准备,持续集成,自动化部署等融为了一体。通过尽可能的全自动方式,使得软件可以一键发布。如果在上线后发现严重的缺陷,还具备迅速版本回滚的机制,由于整个发布流程已经经过了千锤百炼,所以软件发布本身就变得非常轻松和安全了。

1 ne-delivery的设计

1.1 需求分析

1.1.1 关于前端工程化

谈起前端工程化,首先需要谈下软件工程,wiki给的解释是软件工程是一门研究和应用如何以系统性的、规范化的、可定量的过程化方法去开发和维护软件,以及如何把经过时间考验而证明正确的管理技术和当前能够得到的最好的技术方法结合起来的学科。谈起软件工程化,通常有以下几个特点:

  • 有IDE开发环境的宿主支持,进行工程的结构与组织的初始化、工程的调试、工程的编译打包等工作。
  • 有可约定的一套工程目录结构,规定软件项目运行所依赖的各类别的资源存放地址,还有编码的风格约定等
  • 软件项目依赖的资源包或许来自团队内部,也或许是第三方的资源,工程化必须要集中对这些资源的管理,并且使资源获取、软件的编译打包、软件的发布等自动且合理的进行着。
  • 软件内部或外部的集成,运维的部署监控等

从以上的特点上可以看出,前端也属于软件工程的范畴,并且是非常肯定的。

在早年间前端甚至没有像Eclipse等为特定的开发语言量身打造的IDE。当时之所以有此现状,是因为针对前端项目的开发和调试即改即刷新已经足够方便,只需要在完成一定的代码修改后启动浏览器刷新看效果即可,所以当时的前端很少会扯到工程化这个概念。在很久的时间内,前端简单的不能再简单了,如一个可运行的前端应用只需要下面的几行代码即可:

图1.1 一个简单的前端应用

但伴随着前后端分离的实践,前端框架的不断创新,承受业务的的增长,其工程也变的复杂而庞大,若按传统的前端项目开发方式进行必然无法有效的纳入管理,代码难以维护、性能难优化、开发成本升高。
所以是时候感谢Node.js了,它让JavaScript这门前端技术成功地突破了以浏览器为宿主的环境限制并且可以跨平台的运行在操作系统之上,这让JavaScript拥有了文件读写和网络输入输出的能力,从此前端工程师们可以根据产品需要定制研发各种辅助工具了。
之后有许多组织陆续发布了很多提供给前端构建定制的工具,随着长时间的实践摸索发现前端工程的两个特性,资源依赖特殊和工程结构复杂使得其工程化任重而道远,构建工具有时也被套在了特定的场合。
前端应用是一个特殊的图形软件,它有两个特殊性:一是前端软件由三种语言共同构成,二是前端应用在客户端运行时是增量的进行着解析。三种语言的相互配合才能保证web应用的流畅运行,增量的解析是优化加载的需要。开发完成后需要输出至少三种格式的静态文件,其产物之间构成了一个复杂的资源依赖关系。
所以,针对前端工程化,需要解决的基础问题如下:
  • 一套集成开发环境
  • 对依赖资源和模块的管理,包括资源的获取、依赖关系处理、实时效果更新、按需进行加载、公共模块抽离与管理等
  • 打通从本地到流水线各个环节,如调试、构建、代理、测试、部署监控等
在某段时间的做法是借助Gulp、Grunt等构建工具。但看其本质,它们只是一个任务的调度器,将功能独立的任务拆解分离开来,按需组合任务。这两种的配置部分每一个任务都需要开发者自行搜索或者自行编写插件解决,而且其在资源的依赖管理方面实在有待完善。
而webpack以一种非常高效的方案解决了前端工程资源依赖管理的问题,大部分的资源依赖关系的处理在它内部已经集成了,所以对于开发者而言,开发者只需要根据项目的场景做适量的配置即可,再结合特定的构建工具和自动化测试框架,就很容易建立一套前端工程化的解决方案。
webpack的定义是模块捆绑,它的目的就是梳理模块间的依赖关系并把它们打包成一系列的静态文件资源,打包原理如下

图1.2 webpack的模块打包原理

webapck简单来说就是依赖一个自定义的配置文件,其进行的所有模块打包工作都是基于这个文件进行的。 配置文件主要分为三大块:
  • entry即入口文件,让webpack用哪个文件作为项目的入口
  • output即出口,让webpack把处理完成的文件放在哪里
  • module即模块,需要用这些模块来处理各种类型的文件及其内容
基于webpack的建立的前端工程,可以自由的根据场景组合各种开源技术栈,如ExpressLessGulp等等,没有那么复杂的资源依赖配置,让持续集成变得更简单,并且工程目录结构也相对地简单和灵活。目前被众多前端团队或开箱即用的Generator所采用。

1.1.2 功能设计

ne-delivery旨在打通前端本地静态资源获得CDN地址这堵墙,这里借助运维搭建的FTP,核心功能也就是FTP上传。

1.2 关键设计

该包需要根据CommonJS规范+es6/7语法开发,提供CLI和Node.js API两种调用方式,主要分为建立FTP连接模块、FTP切换模块、文件过滤模块、CLI调用与提示模块、Node.js调用模块。通过两种方式调用时,根据资源后缀动态切换FTP,并对不可上传的文件进行过滤。有ne-delivery介入的资源发布序列图为:

图1.3 有ne-delivery介入的资源发布序列图

两种调用方式需要的参数:

  • 本地资源路径
  • 远程资源路径

对于上传成功的文件需要返回文件列表。

2 ne-delivery实现

2.1 创建项目仓库

关于代码提交,CD提倡在每次commit时,团队的开发者可以在流程中获得针对此次提交最初始的反馈。当有团队开发者往项目仓库中提交新变化时,立即会自动触发后续的管道。如果某个阶段出现错误,整个管道便会立即自动中止本次发布并及时的通知到开发者。团队根据随即修改的代码,对这次失败进行结对审查,然后提交新的代码。代码提交后,便会再次自动触发上述的流程。倘若本次从始至终的管道流程正常,会自动进入下一阶段,在一定程度上也增加了团队对发布流水线的信心。

 

图2.1 CD基础管道流程

创建并进入ne-delivery文件夹,依然使用npm进行包与依赖的管理,执行npm init初始package.json,并以此创建lib、bin、__tests__文件夹及README等一些配置,并在主干上直接开发。(详细代码不再赘述,贴代码并不是写本文的重点)

2.2 自动化测试

以往很少有在实践自动化测试,代码层面模拟各种分支情况便完成了。但随着接口逻辑复杂度的提升,以往的测试方式总会遗漏一些缺陷,也会使交付周期变长。对于有测试用例的项目,虽然不能保证百分百的无缺陷,但至少测试覆盖到的地方是没有问题的。自动化测试的另个重要的特点便是可以使团队快速的得到反馈,反馈的速度意味着开发效率的高低。自动化测试也要基于投入与产出比来做,通常来说写少量的测试用例,覆盖到百分之八十及以上的场景即可了,之后随着迭代,也应该有测试用例的沉淀,并且尽可能的要提高覆盖率。自动化测试主要包括测试框架、断言库、代码测试覆盖率工具以及覆盖率报告,主要分test-suite(unit-tests)和e2e-tests两个流程。

关于测试框架的选择目前比较热的有Mocha、Jasmine、Jest等,不同的测试框架支持的测试风格也不同,如基于对测试驱动开发(TDD)和行为驱动开发(BDD)支持的比较,Mocha和Jest两者都支持,而Jasmine只支持后者。

代码覆盖率在Node.js里首选istanbul,其原理就是为代码在语法层级上为分支打点,当运行了打点后的代码,它会根据运行结束后收集到的信息和打点是的信息进行对比来统计出当前的测试用例的对被测代码的的覆盖情况,同时产出覆盖率报告。

Jest为例,执行jest --coverage就可以在coverage目录下生成测试报告,因为Jest已经集成了istanbul,所以可以直接使用--coverage参数,jest会自动的递归__test__目录下的用例,并返回最终的结果。关于测试报告可视化之后会在持续集成部分提到。

2.3 项目构建

构建产出将要发布的目录或文件,然后将需要发布内容传到管理的环境中,以供后续的部署或分发,之后的每个阶段在其运行中都会触碰到这次构建出的内容。构建阶段必须再次执行测试用例,随后便开始集成测试以及必要的静态代码分析,在以往的流程中,发布到线上环境的中的文件可能会与测试通过的的文件并不是同一份,因为在每个阶段中都有可能进行一次自我的构建,那么差异也就在此时产生了,修复这种缺陷是很有难度系数的。因为在团队内部能正常运行的软件,发布到线上却不能运行。而CD流程管道便可以消除这种缺陷,当某处发生了任何错误,管道便会立即中止并及时反馈到团队。

2.4 持续集成

持续交付的基础就是持续集成(Continuous Integration,CI)了,CI提倡团队内部每天都可以进行多次触发去集成他们的更新。并且每次的集成都是通过自动化的构建结构来验证,包括自动测试、自动编译、自动分发等,从而使团队尽快地发现并处理集成时的错误。

以Jenkins为例,需安装email-ext(邮件通知)、cobertura、htmlpublisher(测试覆盖率报告可视化)、embeddable-build-status(集成状态徽章)四个插件,构建一个自由风格的软件项目(Pipeline也可以)。配置项目如下:

图2.2 选择一个Node.js版本

图2.3 构建过程

图2.4 代码覆盖率报告及度量指标的阈值

以及其他的构建自定义,比如构建hook、Email Notification等,手动触发一次集成,整个过程会生成日志(某次集成log.txt),并且相应的报告也可视化。

图2.5 Cobertura Report

图2.6 JS Coverage HTML Report

图2.7 集成反馈邮件

2.5 手工验收测试

虽然在进行着自动化测试的完善,但在特定的场景下手工测试还是必须的。以往,测试人员需要自己搭建一个人工测试的环境,这让测试人员感觉十分痛苦,因为其中手动部分包含着太多容易出错的步骤。CD提倡管道应该自动搭建测试使用的基础环境,并通知测试人员可以访问已部署的应用相关信息。当测试人员确认质量通过后,便可将内容从待发布候选状态推进为发布候选状态。此时,软件已经做好了充足的准备,等待发布到流水线上。

2.6 自动部署

部署有时会因为脚本的失败或部署环境欠佳,部署过程花费了超过开发者一倍的时间。CD提倡尽可能自动的环境搭建,涉及到部署的配置以及和他相关的脚本,已经在真实的管道过了多次验证。正因此,从本地环境部署到线上环境流程中的绝大多数错误都已经被捕获到了。

私有Node包以发布到远程Sinopia为目标,为了快速产出Sinopia环境,这里选择Sinopia的一个docker镜像(keyvanfatehi/sinopia),接着添加用户并将自己的.npmrc复制到Jenkins的目录,此时构建步骤的npm publish生效。再次通过一个commit触发整个管道,所有阶段通过后会自动部署。

图2.8 ne-delivery发布至Sinopia

2.7 整合

经过以上的活动,只需要本地进行一个可靠的提交,便会触发一次CD,Jest进行自动化的测试并产出报告,Babel编译项目,npm部署最终产物至Sinopia,Jenkins会根据配置执行环境重建以及报告收集和集成状态反馈。并且所有的管道都应尽可能的是自动的可视的。

3 总结

3.1 实践成果

将CD实践于日常开发与维护的前端应用,使整个发布流程变得更加的可靠,降低了发布的风险。同时伴随着发布频率的提高,每次发布中所涉及到的代码更新量也在减少。因此使团队定位问题也变得没有以往的那么困难,从而节约了时间人力等成本。从识别原始的需求再到在生产环境中的产品部署,需求在以最小批量的形式顺畅流动在团队中的各个角落,能够在较短的周期内完成需求频繁的小粒度交付提供真实的用户来使用,在为用户带来价值的同时,也可以快速的得到反馈验证新发布中涉及的商业价值,并激励软件新的变化产生。持续的发现并对开发流程中的缺陷进行改进,从而促进了开发人员,测试人员,运维人员以及运营人员之间的协作,团队能够在更短的时间将有价值的变更发布到上线。

3.2 流程难处

一个交付流程牵连到公司内部的多个组织,每个组织都有已有自己的工作方式和利益。要想引入CD,以最大程度的帮助组织平稳的过渡到CD流程范围内,存在着极大的挑战。比如某一个已准备好进行发布的更新必须通过某个变更委员会的批准,这会导致它的发布时间延长,假如需求从提取到发布只需要几天的时间就能够顺利完成,那么批准需要的时间对于这次更新的整个周期来说也是是相对漫长的。再次就是针对现有的大型整体性的应用,我们需要使用很多不同的服务与技术栈作为依赖,在这样的场景下,需要在广泛被接受的标准之上,依赖于开放的私有服务进行建设,并创建一种灵活的生态系统,让更多的组织能够简单的从CD中受益,需要长时间的摸索。


参考文献:

[1] (英)亨布尔(Humble,J.),(英)法利(Farley,D.)著;乔梁译. 持续交付:发布可靠软件的系统方法. 北京:人民邮电出版社,2011.10.

[2] (美)马丁著;邓辉译,孟岩审. 敏捷软件开发:原则、模式与实践. 北京:清华大学出版社,2003.

[3] S. Neely and S. Stolt. Continuous delivery? easy! just change everything (well, maybe it is not that easy). In Agile Conference, 2013.

本文来自网易实践者社区,经作者王宁授权发布。