版权归作者所有。商业转载请联系Scott获得授权,非商业转载请注明出处【务必保留全文,请勿删改】。Node的工具价值不多说,但服务的价值需要长期去挖掘。小菜的前端还是服务路上的小学生。目前的尝试是Cross框架,尝到了甜头。我想几乎没有一个前端工程师不会对Node感兴趣,但是对于它适合做什么,每个人都有不同的答案。但是只有少数团队有勇气让Node去做服务端的事情。之所以缺乏自信和勇气,是因为Node还没有一个足够好用的框架让你快速证明驱动业务的价值。在侵蚀服务器领域时,你会受到挑战甚至刁难。这也成为团队推广Node的普遍阻力。希望大家能从小菜团队中找到一些答案,其中一部分答案是支持Node。只有有了足够的理解和认知,才能针对一般性问题抽象出一般性的解决方案并付诸实施。在小菜里面,就是对Node框架的封装。这个框架还没有开源,名字叫Cross,寓意没有过不去的技术门槛。明确区分Node.js的前端和后端团队。前端与前端团队之间的关系是相爱相杀。它是左右手的组合。接口联调既有上游数据位也有下游数据位,也有一次次精诚合作才能赢得项目的战役。在服务方面,前端直接介入服务器领域,而从整个行业来看,这种介入在大中型公司已经成为不可阻挡的趋势,不管是淘宝,天猫,支付宝,腾讯,网易,百度,包括创业独角兽大搜车,贝贝,Rokid,国内外很多公司,不分国籍,不分领域,都有团队在深度工作,所以这里的第一个边界就是前端边界.一千家企业可能有一千种商业模式,一千种用户画像,一千种商业特征。不仅有重度依赖算法和高实时计算的井喷接入场景,还有每天几十个UV、几百个PV。ToB大客户产品,什么场景适合用Node,什么不适合,这个第二个边界就是Node在业务领域的服务边界。只有厘清这两个边界,Node才能生存下去。没有这两个边界,难免处处碰壁,无法落地。关于前后端边界,我曾经写过这么一段话:数据的控制和视图依赖的API的控制,这里是目前的前后端边界,数据控制属于后面-end,API属于后端,简单的把前端和后端看成一个完整的系统,在这个系统中,从API向下到后端,从API向上到后端属于前端。API下,业务流和数据流的逻辑,以及上面数据的调用和组装,是数据层面的天然分界点。Node在植入时,还必须在API层面与Java保持规范的统一和集成。兼容性,边界只能通过RPC无缝调用来讨论,这个边界可以是非强业务耦合,比如独立的内部协作系统,也可以是非高计算类型,可以是相对独立的异步高层并发模块,比如消息栈的频繁拉取和推送,比如日志的收集和排序等,可以概括为非复杂的业务流程,非高计算类型可以作为Node.js的入口边界。至于业务的服务边界,只要是小而美的相对独立的系统,只要不是核心业务,都可以用Node快速开发。例如,有报表系统、打包系统、发布系统、市场调研系统、日志系统、可视化平台、求职面试系统、错误跟踪系统等等。在仔细评估以上两个边界时,一定不要忘记自己团队成员的能力配置,是否能hold住Node,是否有Node技术专家负责,否则贸然使用可能会适得其反。为什么要封装Cross?弄清以上边界后,小菜前端深入使用Node一年多,从基础设施体系到相对独立的业务体系。整个过程下来,团队中更多的同学掌握了Node。同时,各个系统之间的差异也越来越大。有的用Koa,有的用Koa2,有的用Thinkjs,有的用Express,有的用原生NodeJS。很显然,每个人的喜好不同,代码质量不同,工程架构方式也不同,这给后期维护带来了巨大的麻烦,尤其是在做Node监控的时候。也不可能做横向快速扩张。我们需要选择一个基于它的框架进行统一封装,这样前端涉及到的所有服务器搭建就可以统一起来,而实际情况是我们的前端和Node应用都是由于整个项目的搭建和服务部署方法。差异已经分散在各个服务器上,导致维护成为瓶颈,是时候做出改变了。这是当时部分零散的应用图:为什么选择Eggjs小菜前端使用Eggjs作为Nodejs的基础服务框架我之前用过Koa、Express、Koa2、Thinkjs等框架,最接近的一个Eggjs是Thinkjs,是齐舞团开源的。相同约定大于配置,基于Koa2完善相同封装,同样采用多级分层。设计方式(Controller、Service等)让应用开发更加清晰明了。不过有意思的是Thinkjs(2013年)的开源时间早于Eggjs的开源时间,而且它在github上star的增长率是远远落后于Eggjs的,而且NPM的下载量也是相同。虽然thinkjs的开发体验也不错,但是小菜之所以最终选择了Eggjs作为Nodejs服务框架。除了以上提到的优势外,还有以下几点:高度可扩展插件机制,方便定制上层框架。丰富活跃的社区生态。渐进式发展。多进程管理。小菜的前端从2018年初就开始使用Eggjs,我们的很多项目都是基于Eggjs构建的,包括我们的报表系统、GraphQLGateway、小程序后台服务等,在使用Eggjs开发这些的过程中项目中,我们逐渐形成了一套适合宋小菜的基于Eggjs的上层框架。基于小菜特定业务场景成长起来的Framework,定制化程度高。可以参考我们实现这个框架的技巧和方法,应该是通用的。秉承什么样的设计理念授人以鱼不如授人以渔。让我们先分享一下我们的设计理念。这是开头最简单但最重要的部分。我们的目标是风格统一,易学易维护。:再就是对整体需求的梳理和开发集成,在开发集成过程中不断优化:设定目??标和设计流程后,就要为具体的实施做准备。我们的实现涉及流程,主要从以下四个方面:Framework关系通用API插件自定义项目管理如何设计Framework框架关系我们把所有常用的API,常用的工具功能,常用的插件(redis,gateway)都集成进去基础框架baseFramework,因为Egg支持多级框架继承,所以我们可以在基础框架baseFramework的基础上衍生出GraphQL相关框架,微服务相关框架等其他框架。相当于一个可以在不同方向定制的框架种子:通用API1。统一获取请求参数假设一个HomeController有成员函数testAction既要处理post请求又要处理get请求,可能会出现以下情况:const{Controller}=require('egg');module.exports=classHomeControllerextends控制器{testAction(){const{ctx}=this;const{方法}=ctx.request;constid=方法==='GET'?ctx.request.query.id:ctx.request.body.id;...}}我们可以将其优化为:/*yourapp/app/controller/home.js*/const{BaseController}=require('egg');//或者const{BaseController}=require('your-egg-framework');module.exports=classHomeControllerextendsBaseController{testAction(){constid=this.获取参数('id');//...}}/*egg-baseframework/core/base_controller.js*/const{Controller}=require('egg')module.exports=classBaseControllerextendsController{getParam(key)复制代码{const{ctx}=这个;const{方法}=ctx.request;if(method==='GET'){if(key){...}else{...}else{...}}}/*your-egg-baseframework/lib/index.js*/const{BaseController}=require('../core/base_controller');module.exports={BaseController,...}/*your-egg-framework/app.js*/module.exports=(app)=>{require('egg').BaseController=BaseController}2.返回数据格式方法同上,我们可以在BaseController中统一定义调用成功和调用失败返回函数,在函数中处理返回数据到避免返回数据不规范的问题。3.通用工具功能我们可以将业务开发中可能用到的工具功能统一定义为内置对象助手的框架扩展形式最重要的是,这些可以通过框架扩展(extend)的形式集成,比如如参数转换,错误信息格式化等。4.添加参数验证层我们可以将参数验证的步骤抽离到逻辑层。有两种方式:在框架加载的时候调用app.loader.loadToContext将controller对应的所有参数验证函数挂载到context中,在controller执行对应的时候在你的框架继承的appWorkerLoader中调用处理函数覆盖eggjs的loadController,对每个controller的处理函数使用相应的逻辑代理插件定制。Egg拥有丰富的插件生态,但总有一些我们需要使用的插件不符合我们的要求,比如:egg-redis长期不支持哨兵模式。egg-graphql不支持连接到其他graphql服务。egg-kafka好久没有维护了。这时候就需要我们自己编写或者修改相应的插件,一些公司层面常用的功能如:Java服务端网关请求(egg-gateway)、用户认证(egg-auth)等。我们也将它们封装成插件,集成到基础框架中。说实话,在整个框架的开发过程中,最后最有成就感的部分是写插件的:项目管理由于插件之间、插件与框架之间、框架与框架之间存在相互依赖关系,代码管理成了一个令人头疼的问题。推荐使用目前流行的monorepo进行管理。规范版本发布流程,避免不兼容问题。综上所述,我们在Cross的建设上投入了将近一个月的时间。从投入产出比上来说,这是一种性价比很高的尝试,但是我们在实施的时候会遇到很多问题。从人和团队的角度来看,这样一套Framework需要有一定Node编程基础的同学才能用好。每个人还是有一定的心理成本的。是否有可能继续降低这个成本?GoingtoPass和high-level的只关心业务逻辑,不关心实现背后的阶段。这是一个值得研究的课题。另外,从事物的角度来看,如果业务上没有那么多的场景去承载这个框架,其实是很难继续往前推进的,因为没有足够多的应用和测试场景来暴露问题。这也是我们目前遇到的一个实际困难。Node缺乏好手阻碍了我们的进步。不过好消息是下一个业务场景已经铺开,团队刚刚进入选好Node播放器,看看应用后效果如何。在这几年的面试或者线下线上的技术分享中,Scott遇到了太多的前端同学。由于团队原因/个人原因/职业成长/技术和管理渠道,甚至家庭城市等,理想国与现实之间,他正在放弃。坚守之间,摇摆不定,心碎不已,可以和我聊聊南北,让你更多地看到和理解工程师的命运和价值。Scott的微信:codingdream,你也可以关注Scott来关注我的动态。
