Open Juliiii opened 5 years ago
最近serverless愈来愈火,我刚好在培训,比较有时间去尝试一些新东西,所以趁这个时候去使用下serverless,尝试使用typescript和nodejs开发,部署在腾讯云scf上的一个小工具,探讨下typescript + nodejs + scf的最好实践模式,并同时抛钻引玉,希望有同学提供更好的方案。
由于本人平时会追一些小说,动漫,电视剧等,但是它们更新的时候,我一般是不感知的,都是等我得空或者睡前的时候,我才会去它们的网站去查看下它们是否更新。如果有这么一个工具,能够在它们更新的时候,告知我,它更新了,更新了啥,那么我就不需要老是用手机去查询,对我起到了一定的便利作用。
这放在我没有接触到serverless之前,我的想法是这样的:写一个这么的程序是不难,但是我得去买个机器去部署啊;如果有问题不能及时发现同时又得上机器查日志;还得自己去控制程序定时爬取的逻辑等等等。总的来说就是,实现与维护一个这样的程序的成本远大于了其带给我的便利,让我有想法却懒于行动。
但是了解serverless的概念后,以上提到阻碍我行动的问题变得不再是问题,例如部署难题,使用serverless就是使用云供应商提供的开发者工具创建函数,打包上传代码即部署成功;又例如定时爬取逻辑,使用其提供的定时触发器能力即可,都大大方便了我的开发,让我更专注于代码实现。这里我不会很官方地去说serverless的概念以及好处,仅是从一个开发者的角度去阐述我的想法。
流程图
程序的整个流程图如下图所示,逻辑很简单,这个项目的目的不在于实现一个多厉害的功能,而在于ts + node + scf的实践方式的探索。
开发
开发能在scf运行的nodejs程序的其实与传统的开发nodejs程序在语言编写上并没有太大区别。比较明显的不同在于,我们开发时得有一个入口的函数,比如像这样:
更具体的入门文档,可以看此处,跟着文档一步步学习编写一个简单的函数。接下来回归正题。
环境搭建
首先为了方便开发,建议安装腾讯云scf提供的命令行工具或者vscode插件。但是这里我开发的时候vscode插件还没发布,所以这里主要使用命令行工具,命令行工具的安装与使用的文档,具体可以看此处。
安装好后,使用 scf init(具体参数得去看文档填写,这个提个建议,scf init提供交互式操作,采取问答的模式去创建)创建一个项目、项目文件很简单,一共就四个文件,前三个,应该不多做介绍。第四个文件template.yaml称为模板文件,简单来说是描述这个函数的文件,比如函数的环境变量,触发器类型等等,具体还是前往文档处查看吧。
scf init
template.yaml
接下来,就是正常配置tsconfig.json,如果没有安装typescript的同学请去官网安装,然后tsc --init就可以快速生成一个tsconfig.json,然后根据自己的需求配置即可。
tsc --init
然后,就是编写npm scripts。
主要就三个操作,build,dev,deploy。
可以使用npm scripts把typescript的编译和scf cli的本地调试,打包和部署串联在一起,使需要敲打的命令简洁和语义化
最后,将本地仓库与远程仓库关联起来。(这里提一个优化:有一种场景是用户已经创建了一个git仓库,现在需要将仓库里的代码写成scf模式下的代码,并配合scf cli使用,目前scf cli只支持init一个完整的项目,如果支持在一个已有项目中快速生成调试和部署的yaml,对开发者来说是一个比较方便的功能)
编码
我的主要逻辑代码分为上面的文件。
上面简单介绍下主要逻辑代码的文件,具体的实现,有兴趣可以移步到 github地址 查看
调试
上面也有提到我编写的npm scripts里有npm run dev的一条。本人开发这个项目时,调试都执行npm run dev来进行调试。这里提一下,测试环境一般是需要和正式环境隔离的。所以可以新建一个 env.json文件,里面填写
{ "NODE_ENV": development }
并将npm script中的dev命令改成 npm run build && scf local generate-event timer timeup | scf native invoke --template template.yaml --env-vars env.json
npm run build && scf local generate-event timer timeup | scf native invoke --template template.yaml --env-vars env.json
然后在配置文件中根据process.env.NODE_ENV变量来判断是测试环境还是正式环境,并填写对应环境依赖的服务的配置即可。
process.env.NODE_ENV
部署
上面讲了这么多,其实都不是我最想表达的,因为我并没有在上面遇到一些很棘手的问题。而在部署的时候,我才发现在使用typescript时,无法在腾讯云scf目前的部署要求以及项目的文件目录管理中做到完美的配合。
后面和同事讨论后,还是有不错的方法是达到两者的平衡。下面是我的多次尝试的一个过程。
如果不使用typescript,仅使用js编写nodejs程序,则不需要编译的过程,部署函数时,只需要打包然后部署即可;但是使用typescript后,则多了一步将ts代码编译成js代码的步骤。为了管理好项目的文件目录,我倾向于ts和js文件分别存放在不同的文件夹,例如,src文件夹存放ts文件,dist则是编译后得到的js文件。我一开始的文件目录便是如此。
文件目录:
tsconfig.json
指定编译src文件夹下的ts文件,输出到dist文件夹
CodeUri指向dist文件夹
根据上面的配置,在本地调试是可以的。但是当部署到云上,测试是失败的。如果大家熟练的话可以立刻发现问题所在,打包没有把node_modules打包进去。主要逻辑代码依赖的第三方库全都找不到,测试当然失败了。
根据第一次尝试,我使用npm scripts的pre钩子,在执行部署前,编辑ts代码,同时把node_modules拷贝到dist文件夹,然后再打包部署解决了这个问题。
package.json
copy_node_modules.js
dist文件夹下的文件
虽然这样做可以运行了,在本地文件目录管理合理,但是提交到云上的代码是编译后的,基本没啥可读性,就是一坨能运行的东西,项目代码也不完整。所以个人认为,最完美的是本地开发的项目代码和交到云上的项目代码是一致的,不需要通过额外的脚本去阉割。虽然目前腾讯云scf控制台的webIDE还只是能看入口文件,不过之后会接入cloud studio,起码可以看到整个代码文件夹的每个文件,说不定以后就支持在线支持typescript编译(虽然不知道可不可能)。所以本人开始了第三次尝试。
我有一个想法:template.yaml中指定的Handler,即入口函数,从index.main_handler 写成 文件夹/index.main_handler,即入口函数可以在某个文件夹里。
我在template.yaml处的Handler写成dist/index.main_handler,CodeUri写成了根目录,这样就可以打包整个文件夹,然后指定Handler为dist文件夹的index文件的main_handler函数。
本地调试时,是成功的!
但是在部署的时候,
额,好吧,我觉得是这个方案是不行的了,因为不符合scf的要求,通过不了校验。
这是我第四次尝试。但是不是最完美的,在文件管理退了一步,允许ts和编译后的js放在一起。这样能做到把整个项目都打包上去,而且可运行,但是ts和js放在一起,文件管理不太合理。修改的地方如下:
index.ts文件从src文件夹移动到根目录
编辑根目录下的index.ts和src文件夹下的ts文件,剔除node_modules,输出到根目录
CodeUri改成根目录,Handler改成 index.main_handler,即跟cli生成的一样
编译后结果
最后部署到云上scf,是可以运行的,而且是把整个项目都打包了上去,日后腾讯云scf接入了cloud studio,webIDE看到的文件架构和本地看到的文件架构是一致的。
兜兜转转,有时候问题解决很简单。和组内同事讨论后,一位大佬同事点出:
可不可以在根目录写一个index文件,然后调用编译后的index文件的入口方法?。
一语惊醒梦中人!是的,一开始就没注意到,还可以这样解决,思维一直在一个圈子里绕来绕去,没有跳出来。这样做的成本很低,而且能达到了我之前说到的理想状态:
本地开发的项目代码和交到云上的项目代码是一致的,不需要通过额外的脚本去阉割
实施方法即是,把typescript文件放在src文件夹下,编辑后的js文件放在dist文件夹下,在根目录编写一个index.js文件,文件里的main_hanlder方法调用编译后的index文件的入口函数,下面是一些核心代码。
index.js
template.json
简单展示下代码线上运行后的结果。
上面说了这么多,这里给一个总结就是:
虽然腾讯云scf没有原生支持typescript,但是经过一些方法还是可以做到两者的完美配合。
首先本地开发是没啥问题的,上面提到的尝试,都是为了能够在本地调试成功的同时可以部署到云上。
主要是部署的问题,其中可行的三个尝试:
第一个是通过一些额外的方法去适配,但是做不到云上的项目和实际的项目的一致,如第二次尝试。
第二个是文件管理上退了一步,不做到极致的分明,如第四次尝试。
第三个是在根目录写一个index.js文件,调用具有真正逻辑的入口函数,做个转发,如第五次尝试,也就是本人认为目前最好的实践方式。
最后,以上的五个尝试,是本人开发的时候的想法与实践,也许不太正确,有误欢迎大家来批评。如果大家有更好的方法,欢迎讨论。五次尝试的源码都在github仓库,前四次尝试均有对应分支,master分支为第五次尝试。
目的
最近serverless愈来愈火,我刚好在培训,比较有时间去尝试一些新东西,所以趁这个时候去使用下serverless,尝试使用typescript和nodejs开发,部署在腾讯云scf上的一个小工具,探讨下typescript + nodejs + scf的最好实践模式,并同时抛钻引玉,希望有同学提供更好的方案。
项目介绍
想法
由于本人平时会追一些小说,动漫,电视剧等,但是它们更新的时候,我一般是不感知的,都是等我得空或者睡前的时候,我才会去它们的网站去查看下它们是否更新。如果有这么一个工具,能够在它们更新的时候,告知我,它更新了,更新了啥,那么我就不需要老是用手机去查询,对我起到了一定的便利作用。
这放在我没有接触到serverless之前,我的想法是这样的:写一个这么的程序是不难,但是我得去买个机器去部署啊;如果有问题不能及时发现同时又得上机器查日志;还得自己去控制程序定时爬取的逻辑等等等。总的来说就是,实现与维护一个这样的程序的成本远大于了其带给我的便利,让我有想法却懒于行动。
但是了解serverless的概念后,以上提到阻碍我行动的问题变得不再是问题,例如部署难题,使用serverless就是使用云供应商提供的开发者工具创建函数,打包上传代码即部署成功;又例如定时爬取逻辑,使用其提供的定时触发器能力即可,都大大方便了我的开发,让我更专注于代码实现。这里我不会很官方地去说serverless的概念以及好处,仅是从一个开发者的角度去阐述我的想法。
实践
流程图
程序的整个流程图如下图所示,逻辑很简单,这个项目的目的不在于实现一个多厉害的功能,而在于ts + node + scf的实践方式的探索。
开发
开发能在scf运行的nodejs程序的其实与传统的开发nodejs程序在语言编写上并没有太大区别。比较明显的不同在于,我们开发时得有一个入口的函数,比如像这样:
更具体的入门文档,可以看此处,跟着文档一步步学习编写一个简单的函数。接下来回归正题。
环境搭建
首先为了方便开发,建议安装腾讯云scf提供的命令行工具或者vscode插件。但是这里我开发的时候vscode插件还没发布,所以这里主要使用命令行工具,命令行工具的安装与使用的文档,具体可以看此处。
安装好后,使用
scf init
(具体参数得去看文档填写,这个提个建议,scf init提供交互式操作,采取问答的模式去创建)创建一个项目、项目文件很简单,一共就四个文件,前三个,应该不多做介绍。第四个文件template.yaml
称为模板文件,简单来说是描述这个函数的文件,比如函数的环境变量,触发器类型等等,具体还是前往文档处查看吧。接下来,就是正常配置tsconfig.json,如果没有安装typescript的同学请去官网安装,然后
tsc --init
就可以快速生成一个tsconfig.json,然后根据自己的需求配置即可。然后,就是编写npm scripts。
主要就三个操作,build,dev,deploy。
可以使用npm scripts把typescript的编译和scf cli的本地调试,打包和部署串联在一起,使需要敲打的命令简洁和语义化
最后,将本地仓库与远程仓库关联起来。(这里提一个优化:有一种场景是用户已经创建了一个git仓库,现在需要将仓库里的代码写成scf模式下的代码,并配合scf cli使用,目前scf cli只支持init一个完整的项目,如果支持在一个已有项目中快速生成调试和部署的yaml,对开发者来说是一个比较方便的功能)
编码
我的主要逻辑代码分为上面的文件。
上面简单介绍下主要逻辑代码的文件,具体的实现,有兴趣可以移步到 github地址 查看
调试
上面也有提到我编写的npm scripts里有npm run dev的一条。本人开发这个项目时,调试都执行npm run dev来进行调试。这里提一下,测试环境一般是需要和正式环境隔离的。所以可以新建一个 env.json文件,里面填写
并将npm script中的dev命令改成
npm run build && scf local generate-event timer timeup | scf native invoke --template template.yaml --env-vars env.json
然后在配置文件中根据
process.env.NODE_ENV
变量来判断是测试环境还是正式环境,并填写对应环境依赖的服务的配置即可。部署
上面讲了这么多,其实都不是我最想表达的,因为我并没有在上面遇到一些很棘手的问题。而在部署的时候,我才发现在使用typescript时,
无法在腾讯云scf目前的部署要求以及项目的文件目录管理中做到完美的配合。后面和同事讨论后,还是有不错的方法是达到两者的平衡。下面是我的多次尝试的一个过程。
如果不使用typescript,仅使用js编写nodejs程序,则不需要编译的过程,部署函数时,只需要打包然后部署即可;但是使用typescript后,则多了一步将ts代码编译成js代码的步骤。为了管理好项目的文件目录,我倾向于ts和js文件分别存放在不同的文件夹,例如,src文件夹存放ts文件,dist则是编译后得到的js文件。我一开始的文件目录便是如此。
文件目录:
tsconfig.json
指定编译src文件夹下的ts文件,输出到dist文件夹
template.yaml
CodeUri指向dist文件夹
根据上面的配置,在本地调试是可以的。但是当部署到云上,测试是失败的。如果大家熟练的话可以立刻发现问题所在,打包没有把node_modules打包进去。主要逻辑代码依赖的第三方库全都找不到,测试当然失败了。
根据第一次尝试,我使用npm scripts的pre钩子,在执行部署前,编辑ts代码,同时把node_modules拷贝到dist文件夹,然后再打包部署解决了这个问题。
package.json
copy_node_modules.js
dist文件夹下的文件
虽然这样做可以运行了,在本地文件目录管理合理,但是提交到云上的代码是编译后的,基本没啥可读性,就是一坨能运行的东西,项目代码也不完整。所以个人认为,最完美的是本地开发的项目代码和交到云上的项目代码是一致的,不需要通过额外的脚本去阉割。虽然目前腾讯云scf控制台的webIDE还只是能看入口文件,不过之后会接入cloud studio,起码可以看到整个代码文件夹的每个文件,说不定以后就支持在线支持typescript编译(虽然不知道可不可能)。所以本人开始了第三次尝试。
我有一个想法:template.yaml中指定的Handler,即入口函数,从index.main_handler 写成 文件夹/index.main_handler,即入口函数可以在某个文件夹里。
我在template.yaml处的Handler写成dist/index.main_handler,CodeUri写成了根目录,这样就可以打包整个文件夹,然后指定Handler为dist文件夹的index文件的main_handler函数。
template.yaml
本地调试时,是成功的!
但是在部署的时候,
额,好吧,我觉得是这个方案是不行的了,因为不符合scf的要求,通过不了校验。
这是我第四次尝试。但是不是最完美的,在文件管理退了一步,允许ts和编译后的js放在一起。这样能做到把整个项目都打包上去,而且可运行,但是ts和js放在一起,文件管理不太合理。修改的地方如下:
index.ts文件从src文件夹移动到根目录
tsconfig.json
编辑根目录下的index.ts和src文件夹下的ts文件,剔除node_modules,输出到根目录
template.yaml
CodeUri改成根目录,Handler改成 index.main_handler,即跟cli生成的一样
编译后结果
最后部署到云上scf,是可以运行的,而且是把整个项目都打包了上去,日后腾讯云scf接入了cloud studio,webIDE看到的文件架构和本地看到的文件架构是一致的。
兜兜转转,有时候问题解决很简单。和组内同事讨论后,一位大佬同事点出:
可不可以在根目录写一个index文件,然后调用编译后的index文件的入口方法?。
一语惊醒梦中人!是的,一开始就没注意到,还可以这样解决,思维一直在一个圈子里绕来绕去,没有跳出来。这样做的成本很低,而且能达到了我之前说到的理想状态:
本地开发的项目代码和交到云上的项目代码是一致的,不需要通过额外的脚本去阉割
实施方法即是,把typescript文件放在src文件夹下,编辑后的js文件放在dist文件夹下,在根目录编写一个index.js文件,文件里的main_hanlder方法调用编译后的index文件的入口函数,下面是一些核心代码。
index.js
tsconfig.json
template.json
编译后结果
成果
简单展示下代码线上运行后的结果。
总结
上面说了这么多,这里给一个总结就是:
虽然腾讯云scf没有原生支持typescript,但是经过一些方法还是可以做到两者的完美配合。
首先本地开发是没啥问题的,上面提到的尝试,都是为了能够在本地调试成功的同时可以部署到云上。
主要是部署的问题,其中可行的三个尝试:
第一个是通过一些额外的方法去适配,但是做不到云上的项目和实际的项目的一致,如第二次尝试。
第二个是文件管理上退了一步,不做到极致的分明,如第四次尝试。
第三个是在根目录写一个index.js文件,调用具有真正逻辑的入口函数,做个转发,如第五次尝试,也就是本人认为目前最好的实践方式。
最后,以上的五个尝试,是本人开发的时候的想法与实践,也许不太正确,有误欢迎大家来批评。如果大家有更好的方法,欢迎讨论。五次尝试的源码都在github仓库,前四次尝试均有对应分支,master分支为第五次尝试。