Web开发模式的探索与思考

Category:
发表:
大纲
  1. 1. 前言
  2. 2. 中间层模式
  3. 3. 中间层模式解决方案的实践
  4. 4. 完全分离模式
  5. 5. 总结

最近这几年,随着软硬件技术的发展,Web开发的相关技术也在突飞猛进,跟十年之前甚至五年之前相比,无论是业务复杂度还是技术选型已经完全是两个维度。

本文是一篇随笔,主要阐述一些作者个人对web开发模式的一些认知和思考。

前言

在五、六年前,或者更早的时候,当谈及Web开发的时候,更多的是指代某一种以后端语言为主的技术栈,比如Java Web,Ruby on Rails,Php等技术栈。经过最近几年的web开发相关技术的迅速发展,现在说web开发可能会在不同的场景下会有不同的划分。

典型的,所谓的Web前端Web全栈等最近几年才被逐渐细分出来的技术方向,正是在前面这种大环境下产生的。

言归正传,我们说Web开发,那么什么Web开发?Web开发的本质是什么?

如果在一个比较高的抽象层面来看这个问题,Web开发就是处理客户端请求及服务端响应这两件事。

上图就是一个非常高的级别的抽象。实际的开发中,还会有许多方面是需要考虑的。

我们来稍微丰满一下这个抽象。

大概2008年左右,一个web应用或者web开发的标准模型如下图,

此时,后端主要做的事情是从数据库中拉取数据,在server端生成html模板,然后将生成的模板发送到客户端。客户端收到模板后,在客户端注入js和css,生成dom树,然后渲染成页面呈现给用户。

这种模式现在依然在某些web应用的开发中被使用。这种模式现在更多的被称之为传统开发模式,或者前后端混合开发模式。其主要的一个特点是,页面模板由后端吐出,甚至吐出的模板会带有样式和交互。前端仅仅是将模板丢给浏览器渲染成dom树生成页面而已。有时候,前端也会注入一些js代码为页面的动态交互提供支持。

这种开发模式,我本人是实际经历过的。以前JavaEE、JavaWeb比较火的时候,经常会写一个某某系统,比如学生管理系统、图书管理系统之类的,在server端通过java来操作JDBC链接数据库,拿到需要的数据,然后将数据与jsp组装起来一起发送给客户端,客户端再交给浏览器渲染。后来还经历过php加smarty的开发模式。其实这两种虽然使用的是不同的技术栈,但是开发模式的本质是一样的。

但是,这时候我们会发现,同一个开发者会扮演好几种角色,需要写server端代码,同时也需要写模板层代码,甚至还会写一些js和css代码。这里需要开发者在不同角色中切换。但是专注server端的开发者们往往非常讨厌或者说不擅长写模板、交互和样式,这时候又发展出一种所谓套页面的开发模式。所谓套页面,是指先让专职的前端开发切好静态页面,然后丢给后端开发去模仿然后写出模板,将需要动态变化的地方换成后端模板语法。这种模式已经被证明是低效的,而且并没有合理的应用开发资源。

说到开发效率,在一个大体量的团队协作中,我们应该秉承这样一种思路,就是让合适的人去合适的事,分工协作,流水线作业肯定是最优的方案。

传统开发模式下,我们的确有一些痛点需要解决。从而衍生出了这样一种思路,让前后端的开发分离开来,不同工种只负责各自的事情,然后遵循某一种协作约定,达到高效产出。

上面基本上就是前后端分离的演变历史。

前后端分离的开发模式大概如下图所示,

前后端分离开发模式下,server端从数据库拿到元数据,经过处理后直接吐出数据,而不是模板。client端拿数据之后,在客户端端进行模板渲染生成页面呈现给用户。

相比传统开发模式,此时服务端不再处理模板层的业务,而是直接只提供数据。职责更加单一。此时的服务端可能会根据不同业务需求或者架构差异,还可能会直接提供服务。这里服务的含义往往指代的是微服务。甚至在一些非绝对的前后端分离模式下,服务端还会提供模板片段。

显而易见,此模式下,前端开发需要做更多的事,而且可能会随着业务的增长前端逻辑和代码量会越来越庞大和复杂。这又推动了各种前端框架的涌现,比如Angular这种大而全的框架,比如React这种专注解决某一个层面业务的框架,比如Webpack这种弥补构建功能缺失的框架,等等。各种框架本身又会带动其周边社区的发展,使得整体前端开发圈子呈现技术爆炸式的发展。

除了传统Web开发之外,因为移动互联网的快速发展,移动端开发、微信开发、h5开发等等范畴,也被前端开发者们照单全收。还有,由于NodeJS平台的崛起,前端开发可能已经不仅仅限于浏览器端的开发工作,借助NodeJS平台,以及ExpressKoa等框架,前端开发者已经具备了涉足服务端开发的能力。

这里有一篇文章,足以管中窥豹,前端开发的技术栈广度和更新速度。

所以,可以探讨的内容实在是非常的多,本文不会过度发散,仅仅是探讨Web开发相关的内容。下面会将重心放在pc端的web开发模式的探讨上,以前后端分离为出发点,阐述本人经历过的两种开发模式,以抛砖引玉。

中间层模式

前面有说过,因为NodeJS平台的活跃,特别是一些成熟的服务端开发框架的出现,比如expresskoa等,让前端开发者使用javascript进行服务端程序的开发成为可能,甚至还可以操作数据库。这也给之前一直在浏览器端开发的前端开发者们开辟了新的工作场景,如果再掌握一些数据库,http协议,网络安全,web server等方面的知识,那么我个人感觉前端开发者完全可以将自己的角色切换成一名后端开发,处理一些通用场景下的后端开发应该是没有问题的。

显而易见,这对传统的前端开发者们提出了更高的要求,工作内容涉足的领域相比之前而言更加底层,会更加频繁的去和真正的后台开发者进行沟通,甚至会参与一些约定和规范的制定,这时候又会要求我们能够有一些后端思维,否则你跟别人在沟通事情的时候都不在频道上,那怎么可以呢?!

这部分内容的标题是中间层模式,那么什么是中间层模式呢?我个人的理解是,以前后端分离为出发点,借助NodeJS平台,在web后端与传统前端(UI层)之间增加一层中间层,负责处理数据、模板、业务等内容。它是后端与UI层的桥梁。有的人喜欢称这个中间层为胶水层

下面有一张我自己画的图,我觉得应该可以表达出我想法中的所谓中间层模式,

  1. 首先,这里可能没有明确意义上的后端,取而代之是REST Server、Micro Service等内容。他们的本质依然是对上层提供数据。这些内容都属于backend的范畴。
  2. backend的上层就是frontend。这里所谓的frontend其实是一种宽泛的指代,它表示了一个响应用户请求、交互等操作的集合。在这个集合中,最上层是用户,然后是客户端(浏览器)层,然后客户端的的下面是NodeJS层。此外还会有一个nginx层。
  3. 第一种场景,当用户发起一个页面请求时,浏览端会首先接受这个请求,然后丢给nginx来代理,nginx根据请求的类型将请求转发给对应的NodeJS层服务。NodeJS层内部会解析来自nginx层转发的请求,执行对应的业务类,组装数据,最终输出一个html模板给客户端(浏览器),浏览器端拿到模板之后,注入客户端的js和css代码之后再渲染到成页面,呈现给用户。
  4. 第二种场景,用户在页面发起交互请求时,比如点击按钮发起请求,此时这个请求最终会丢给NodeJS层,NodeJS层收到请求之后进行处理,然后将响应返回给浏览器。
  5. 基本上,绝大部分来自客户端(UI层)的请求和交互都会在NodeJS层进行处理,UI层与底层的REST服务或者微服务是隔离的。当然也有特例,比如图片验证码这种场景,将会由浏览器直接请求底层REST服务或者微服务。比如,
  6. 很多时候,用户通过url请求一个页面时,NodeJS层收到这个请求后,会同时发出多个REST请求,或者向多个微服务发出请求,待所有的REST请求都返回后,在NodeJS层进行数据组装,处理,清洗等操作,这其中可能会掺杂一些特定的业务需求。我们一般称这一过程为REST Combo。比如,

通过上面的描述,这种模式下前端开发们将会处理三方面的事,

  1. nginx层,监控请求及类型,转发请求到对应的NodeJS服务。
  2. UI层,浏览器端实现,各种交互、样式、布局等。
  3. NodeJS层,连接UI层和底层数据提供方的桥梁,同时可能会注入一些偶合具体业务的实现。

更进一步,我们在实际项目中,可以将UI层和NodeJS层不必划分太明确的界限,如下的目录结构,

其中app目录是NodeJS层代码,public目录是UI层代码,dist是项目构建之后的目录。在项目部署时,只会针对dist目录。

除此之外,这种模式下,我们还有许多可以探索和尝试的地方,比如

  1. UI层实现的技术选型其实是比较灵活的,可以用JQuery式的开发方式,也可以引入一些成熟的前端MV*框架(比如NG,React,VueJS等)来提升开发效率。引入这些框架之后,可能会带来许多其他层面需要解决的问题,比如前端构建、前端模块化等等。
  2. NodeJS可以不吐出模板,而是直接吐出数据或者吐出模板片段和数据。将NodeJS当成UI层的数据提供server。
  3. 共用UI层和NodeJS层的部分代码,比如特定的业务代码,逻辑判断,表单校验等等。
  4. 可以将NodeJS层和UI层隔离构建打包,也可以混合构建打包。
  5. ……

中间层模式解决方案的实践

这里我尝试给一个中间层模式的解决方案sword-plus。它的定位并不是大而全的一站式解决方案,而是为了更便利的开发NodeJS层的一系列工具集合以及常用功能的提炼。基于Koa程序,提供了日志记录、模板渲染、请求访问、路由解析、业务类抽象等功能。其内部的各个功能组件存在一定程度上的偶合。所以使用sword-plus的前提是,引入NodeJS作为中间层;底层的框架选型为Koa;NodeJS层向上层提供组装好数据的模板。

sword-plus包括以下几个部件,RouterPuggerConnectorLoggerHandlerSwordError

Router用于解析koa的包装后的请求。其中有两个主要方法,

  1. parse,解析路由
  2. provider,生成业务类,并启动对应路由的业务逻辑

Pugger主要用于组装数据并渲染Jade/Pug模板。

  1. 这里的模板属于服务端模板,在渲染的过程中,可通过参数配置是否记录渲染日志。
  2. 主要方法render,用于组装数据生成服务端模板。
  3. 渲染日志将会记录在渲染过程中的任何报错信息。

Connector封装了NodeJS层->REST层,UI层->NodeJS层的请求操作。内部使用了node-fetch。同时允许在操作请求时,记录请求日志。它有两个方法,

  1. get,执行get请求
  2. post,执行post请求

Logger是日志组件,Logger将所有的日志分为如下几大类(category),

  • fatalerrorwarninfo
  • requestresponserenderaction

其中第一行其实是bunyan自带日志等级的alias,第二行是根据不同业务场景抽象出来的。

  1. request,NodeJS程序向REST服务器发送rest api请求的日志
  2. response,REST服务器返回给NodeJS程序的rest api响应的日志
  3. render,服务端模板的渲染日志,这里所谓的渲染日志其实是跟客户端(浏览器)是没有关系的,它仅仅表示模板文件和数据的组装和编译过程
  4. action,所有由用户发起从而产生的交互日志,包括页面请求、表单提交、客户端ajax请求等等

每一条日志都是一个record抽象,每个record实例在category的维度下,还会有level的区分,常用的level有如下几种infowarnerror

Handler是业务类模型的顶层抽象。所有请求经过Router解析之后,都会通过Handler来派生出具体的业务类。在Handler中可以使用内部扩展或者外部拓展(Handler.inject),来增加所有业务类可使用的功能方法。此外,Handler中对几个套件的实例对象做了代理,使得在具体的业务类中可以使用他们。Handler中提供如下几个方法,

  1. inherits,在Router解析完毕之后,在Router.provider中通过此方法生成具体的业务类Clazz。
  2. dispatch,在业务类中对get和post请求类型进行自适配转发。
  3. inject,在server启动时,可以根据需要注入外部拓展,一旦注入,则在所有生成的业务类中都可使用。

SwordError是SwordPlus的Error封装。用于统一分配error。

目前SwordPlus Error的type有如下几种,

  • LACK_OF_PARAMETER
  • INVALID_OF_PARAMETER
  • 404
  • ERROR_OF_MAKEDIR
  • SUFFIX_NOT_SUPPORT
  • SUFFIX_IS_REQUIRED
  • TIMEOUT
  • MODULE_NOT_FOUND
  • NO_TEMPLATE_FILE

sword-plus的执行流程简图大致如下,

具体的使用可以参考sword-plus中的demo文件夹。

完全分离模式

这里稍微提一下所谓的完全分离模式,即此模式下的前端开发和传统意义上的后端开发完全隔离开,不会涉及到中间层的开发。如下图,

此时,前端和后端通过ajax请求来交互,后端返回给前端数据。客户端的所有事情,包括页面渲染、交互、样式、路由等等都是由前端自己来管理。此时前端开发往往会引入一个较为成熟的前端开源框架作为底层选型。这种开发模式有其独特的适用场景,比如单页应用(Single Page Application)、企业内部的某个管理系统等等。如果前端开发对底层选型的框架较为熟悉,往往开发速度非常快,基本上在实际开发中遇到的一些问题都可以在框架社区中找到解决方案。

这种开发也有其短板的方面,往往随着需求迭代,前端的代码量会越来越多,前端的各种交互和模块化管理会越来越重,越来越不好维护。还有一点就是可供选择的方案非常多,有时候你会不知道到底选择哪种方案好。

我个人的看法是,根据场景和需求来进行实现方案和技术的选型,不要一味的追求新技术和潮流。目前前端圈子中流行的几种主流框架,比如Angular,React,Vue等都会有其优势和弱势的地方。比如,Angular本身是包罗万象的框架,一旦熟悉起来开发速度非常快,但是其入门门槛低深入比较复杂,而且有一些场景是其先天不适合的。React只关注View层的处理,提供了一个非常好的思路告诉我们应该如何做View和Data层的交互和更新,但是其现有的数据状态管理方案比如Redux,我个人感觉一直都不是太好用。VueJS的作者吸收了市面上众多框架的优势,个人感觉它一直都在发展中,本人并没有在生产环境中使用过,不作过多评价。

不过话说回来,个人认为前端圈子目前的快速发展是非常好的,虽然新东西很多,解决同一个痛点的方案可能会有很多选择,但是这并不影响我们去学习他们,去领悟它们的解决问题的思维,甚至去体验一下他们有坑的地方,至于到底要不要再生产环境使用,那是另外一回事了,所以我个人并不厌恶前端圈子的当下的这种现状。

总结

这篇文章的主旨意在阐述一些我个人对当下web开发模式的认知和探索,个人认为文章中的中间层开发模式是一种万金油的开发模式,除了文章中谈到的内容,我也给出了一些可供继续探索下去的点,我认为中间层开发模式基本上可以适配任何需求下的web开发需求。