前端技术架构与工程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.1 前端的时代意义

如果以2005年AJAX的诞生[2]为起点,在迄今为止的十几年时间里,针对前端工程师定位的探索和争论从来没有停止过。前端工程师该做什么不该做什么?发展方向是什么?扩展技能是什么?即使在今天(2019年),这些问题也没有准确的答案。最早一批的专职前端工程师大多是由Web服务端开发者或者UI/UE工程师转变而来的,这两种出身不同的前端工程师分别代表了两个典型的发展方向:Web服务端开发出身的前端工程师普遍偏向于“服务端+前端”,以逻辑见长;UI/UE出身的前端工程师普遍偏向于“设计+前端”,以用户体验见长。

在PC时代,“设计+前端”的模式相较占优。当时智能手机尚未普及,绝大多数的线上业务是以PC浏览器为载体的,并且Web网站的交互逻辑普遍比较简单,以展示为主。前端工程师的主要工作集中于UI的静态表现和动画上。设计师和产品经理在UI和动画上费尽了脑筋,如果负责开发的前端工程师能够对设计有所理解和掌握便事半功倍了。所以PC时代的前端工程师结对编程的伙伴通常是一名UI或者UE设计师,目标是开发出高度还原设计稿的UI和动画。

“PC时代”是一个比较模糊的概念,并没有很准确的划分界限。一种较普遍的观点是,将2010年之前定义为“PC时代”。这是由于自2010年起,Android和iOS系统市场份额急剧上升,智能手机崛起,分流了大量PC用户。

之后智能手机崛起,彻底改变了用户的使用习惯和思维方式。今天人们看到一家陌生而特别的小店,第一反应是询问店家有没有专属的App或者微信公众号而不是网站。在这样的时代背景下,前端工程师的工作重心也发生了倾斜。历史的前进不仅带来了智能手机,也推动着设备硬件性能和浏览器的进化,同时还有HTML5。前端工程师可以踏入视频、语音、VR等新技术领域。从理论上讲,这个时代下的硬件和技术条件能够支撑更精致的UI和动画,但是PC时代火热的“设计+前端”模式反而逐渐式微,甚至有一段时间出现了设计的“返古潮”,即追求极简的设计风格,颇有些类似于文艺复兴时期建筑风格的“仿古潮”。

14世纪文艺复兴时期,意大利的建筑风格出现了一股“仿古潮”。这股风潮反对代表着神权的哥特式建筑,学习以古希腊和古罗马为代表的古典建筑风格,核心理念是体现“自然”与“和谐”。

img

哥特式建筑代表——德国科隆大教堂
——图片引自维基百科

img

古希腊建筑代表——帕特农神庙
——图片引自维基百科

前端工程师的工作重点逐渐偏向复杂的交互逻辑和架构,同时技能和发展方向也发生了改变。在当前的时间节点,前端工程师的发展方向被重新定义,较普遍的有如下两种:

● 服务端 + Web前端

● App前端 + Web前端

Node.js是革命性的,语言的亲和性可以令前端工程师以相对较小的成本涉足服务端开发。以此为起点逐渐发展出了“服务端+Web前端”的所谓“大前端”模式,是目前占比较高的一种趋势。与之形成对比的是“App前端+Web前端”,或者也可以称为“泛前端”,代表性技术是React Native、Flutter以及后续推出的各种类似技术和框架。前端工程师可以使用熟悉的JavaScript、CSS、HTML或者相似的技术(比如开发微信小程序和支付宝小程序所使用的技术)开发原生或混合移动应用。“泛前端”已经脱离了传统意义上的前端范畴,这种模式目前仍然处于探索阶段,尚未成为主流。与其相关的技术也没有发展成熟,未来如何发展尚不可知,但也不失为一个有价值的研究方向。本书所讨论的内容仍然是围绕传统Web领域的前端以及“大前端”展开的。

“大前端”模式下的前端工程师负责一部分Web服务端的开发工作,但是具体负责哪一部分或者说纵深的程度如何却是众说纷纭。有一种声音提倡所谓的“全栈开发”,即前端工程师负责开发客户端逻辑的同时,将Web服务端、数据库管理等工作一并包揽。这是一个美好的愿望,但实际上一个人很难做到从前到后面面俱到。如果仅是类似个人博客这种体量小、架构简单且对稳定性要求不高的网站,“全栈”没有任何问题。但是对于大型Web应用程序来说,每一个环节都必须做到尽善尽美。术业有专攻,专业的事情最好交给专业的人去做。另一种声音认为前端工程师可以负责一部分Web服务端工作,但是仅限于渲染。比如,使用Node.js架设中间渲染层,将前后端模板统一,比较典型的案例是淘宝的Midway Framework。这种模式是实现前后端分离的一种探索,且目前已经得到业界一定的认可和普及。当然,随着相关技术的迭代和进化,其真实的价值和正误如何还有待评定,但就目前的时间节点来说,“大前端”模式已经成为一种主流趋势。

业务逻辑与交互逻辑

Rockford Lhotka在Expert C# Business Objects一书中将应用程序的架构分为5层,由下至上分别为:数据储存层、数据访问层、业务逻辑层、表现控制层和表现层,如图1-1所示。

img

图1-1 应用程序的分层架构

● 表现层(Interface Layer)负责UI和数据的展示、用户行为的交互、用户输入的收集等,对应到Web领域就是浏览器层。表现层的代码是不安全的,JavaScript/CSS/HTML均是明文代码,不论是否经过混淆和加密,它们都可以很轻易地被解析和解读。所以正如其名,表现层的工作仅仅是“表现”。

● 表现控制层(Interface Control Layer)负责路由分发、用户输入响应等,简单来说就是负责控制用户能够看到的内容,对应到Web领域可以理解为HTTP服务器、MVC[3]架构模式中的View以及与渲染功能相关的Controller。

● 业务逻辑层(Business Logic Layer)负责处理和管理所有的业务逻辑,包括但不限于数据验证、权限管理等。对应到Web领域可以理解为MVC架构模式中的Model和与数据处理相关的Controller。业务逻辑层是项目业务的核心。

● 数据访问层(Data Access Layer)负责抽象和封装数据库操作,用于业务逻辑层与数据储存层之间的互动。数据访问层是面向对象理念中“关注点分离”的最佳案例之一,实现了业务逻辑与数据库的松耦合。

● 数据储存层负责数据的持久储存和管理,可以简单地将其理解为数据库管理层,由专业的数据库软件(比如Oracle、MySQL等)承载。

在SPA(Single Page Application,单页面应用)模式出现之前,通常由服务端模板引擎来渲染初始的HTML内容。在某些场景下,模板引擎需要对Controller传入的数据进行二次加工后再渲染成HTML字符串。在MVC架构模式中,通常将View理解为两部分:服务端动态模板与浏览器中的JavaScript/CSS/HTML等静态资源。在分层架构中两者分别属于表现控制层和表现层,统称为交互逻辑层。传统前端工程师的工作核心便是围绕View的静态部分展开的,“大前端”将与渲染功能相关的Controller归属于前端的工作范畴,定义为中间渲染层,如图1-2所示。

img

图1-2 “大前端”分层架构

层级之间的划分是一种架构设计上的约定,各个层级之间往往没有绝对明确的分界线。不同类型、场景、规模的项目在层级上的划分可能会存在很大差异。比如,对于类似个人博客的小规模项目,Model既存在于业务逻辑层同时又可以充当一定的数据访问层的角色。对于大型项目而言,分层架构是实现关注点分离的必要途径。业务逻辑层、数据访问层和数据储存层三者之间的划分相对比较清晰,相关技术和生态也相对成熟,并且不论是传统的前端还是“大前端”均不会纵深到业务逻辑层以下。所以对于前端工程师来说,数据访问层和数据储存层并不是深入研究的对象。随着前端技术的飞速发展,交互逻辑层与业务逻辑层越来越容易产生混淆,如何定义和区分两者是后续所讨论内容的必要前提。维基百科[4]对两者的定义为:

● 业务逻辑是现实业务规则的编码实现,决定了数据被创建、储存和修改的规则。

● 交互逻辑指的是将数据展示给用户的方式。

举个例子:假设某电商网站每隔一段时间会在所有注册用户中随机抽取1名幸运用户参与幸运抽奖活动。被抽中的用户会在下次登录网站时收到一则提示消息,提示的内容是“恭喜您成为幸运用户,请问您是否参加抽奖活动?”。此用户可以单击“是”按钮进入抽奖网页,也可以单击“否”按钮不参与活动。为了提升用户体验,在单击“否”按钮后会弹出再次确认的提示框,以防止用户误操作。如果普通用户进入抽奖网页,则提示其无抽奖权限。此外,此电商网站有中英文版本,两个版本除了显示的语言不同以外,所有的逻辑均完全一致。

将以上案例所述场景的大致逻辑进行简单梳理和划分,业务逻辑包括:

● 从数据库中获取所有注册用户,然后随机抽取幸运用户。这个逻辑在后台执行,用户不可见。

● 将被抽中的用户标记为幸运用户,开放抽奖权限,并且对其推送一条提示消息。

● 接收到客户端发送的用户不参加抽奖的HTTP请求,将此用户标记为普通用户,抽奖活动结束。

● 接收到客户端发送的用户参加抽奖的请求,首先验证此用户是否具有权限。权限验证成功进入抽奖逻辑,否则响应“无权操作”。

● 抽奖逻辑执行完毕之后通知用户抽奖结果,抽奖活动结束。

交互逻辑包括:

● 浏览器端在收到推送之后将此消息以对话框的形式展示给用户。

● 对话框中的文字根据当前用户浏览的版本设定为英文或者中文。

● 对话框中除了消息文字之外,还有两个可单击的按钮,分别为“是/Yes”和“否/No”。

● 用户单击“是/Yes”按钮后浏览器跳转到抽奖网页进入抽奖逻辑。

● 用户单击“否/No”按钮后弹出二级对话框,提示用户是否确定不参加活动。

● 二级对话框有两个按钮,分别为“确认/Confirm”和“取消/Cancel”。

● 用户单击二级对话框中的“取消/Cancel”按钮之后隐藏二级对话框,回到一级对话框。

● 用户单击二级对话框中的“确认/Confirm”按钮之后隐藏所有对话框,并且向后台发送一条HTTP请求,通知后台用户不参加抽奖。

以上业务逻辑和交互逻辑的划分乍看上去很清晰,但是有些逻辑的归属在前端工程师角色发生转变时就会产生问题。比如,上述业务逻辑中的抽奖权限验证环节,在传统的前后端分工模式(即前端工程师只负责浏览器端的相关开发工作)下应该交由服务端执行,这一点没有任何争议。那么如果是“大前端”模式呢?既然前端工程师掌控了Web服务端的一部分工作,是不是应该把抽奖的接口交给前端工程师?这个问题代表的是随着“大前端”的产生和普及产生的一个经典争议:前端该不该碰业务逻辑。

前端的边界

Berners-Lee[5]在编写第一个网页时可能没有想到Web网站的架构和开发人员的职责分配能够发展到如此复杂和精细,也不会想到前后端工程师会争执业务逻辑的归属问题。在前端工程师这个岗位诞生之前,网页的UI和交互操作非常简单,通常由服务端开发者兼顾为之。AJAX技术推动了网页的第一次革命,同时也间接影响了JavaScript、CSS、HTML的进化。客户端的UI和交互复杂度不断提升,从而出现了专职的前端工程师。也就是说,前端工程师这一岗位被独立分化出来的初衷便是负责客户端相关的开发工作。

最初的Web网站用户体量小、业务逻辑和交互逻辑比较简单,没有分层的架构设计。开发团队仅需要针对模块进行分工,不需要进行职责上的划分就可以完成开发工作。随着业务逻辑复杂度和数据规模的增长,开始分化出独立的业务逻辑层以及与业务逻辑解耦的数据层(包括数据访问层与数据储存层)。最后,在原有业务逻辑层的基础上进一步剥离出交互逻辑层,至此便形成了如图1-1所示的5层架构模式。在分层架构的演进过程中,每一次的分层都贯彻了关注点分离原则,将各个层级进行分离、解耦,从而搭建高性能、高可用、可扩展、可伸缩的应用程序架构,同时提高开发团队的迭代和维护效率。

业务逻辑层作为Web网站业务的核心,最主要的原则之一是平台无关性。比如,一个电商平台有Web网站、App、微信小程序等客户端平台,所有平台的核心业务逻辑必须保持一致。如果Web网站的开发团队施行“大前端”模式,那么所谓的“大前端”是否应该涵盖数据接口的开发和维护?假设Web网站出现了bug需要修改服务端的HTML模板,是否应该牵涉与渲染无关的服务端代码?相反,如果数据接口出现了bug,是否应该牵涉与渲染相关的服务端代码?

单纯从技术角度考虑,将代码按照是否与渲染相关进行分隔,或者使用微服务架构将渲染功能分离和解耦都可以解决以上问题。但是即使抛开技术架构上的耦合,开发效率也是一个很大的问题。分层架构最典型的优势之一是负责各层级的开发者可以实现并行开发,如果前端工程师同时负责交互逻辑和业务逻辑,这不仅丢失了Web前后端并行开发的优势,同时还需要配合其他客户端平台的联调和测试工作,严重拖慢了整体的工作进度。

可能会有一部分前端开发者反驳“唯历史论”,认为以现在的前端技术和生态完全有能力承担业务逻辑层的开发。针对这种观点需要强调的一点是:架构的分层是一种设计理念,意在解耦,而不是以承载各层级的技术“能力”为界限。在此所讨论的“前端的边界”也并非指的是“前端工程师的能力边界”,而是讨论前端在Web分层架构中的位置。业务逻辑层不属于前端范畴并不代表前端工程师不能接触业务逻辑,职称只是一个代号,实际工作应根据团队组织架构和产品需求而定。比如依照上面所述,如果某些产品对于数据接口的需求是仅支持Web平台,则前端工程师负责这部分业务逻辑可以在一定程度上提高开发和迭代效率,虽然这种需求并不普遍。历史不断推进,技术和生态也不断进化,分层架构也只是目前时间节点的一种相对较优的实践方案,具有时代的局限性。但起码目前来看,这种层级的划分是比较合理的。所以在当前的时代背景下,前端仍然是围绕交互逻辑展开的。本书后续所讨论的全部内容均基于此。