跳至主要內容

前端架构-架构和前端架构

Mr.Finn大约 26 分钟前端读书架构

此系列文章,记录在黄峰达的《前端架构-从入门到微前端》一书中的所读所学,此系列文章仅供个人记录学习,作为读书笔记使用。

没有一种架构能满足未来的需求。

在软件的生命周期中,架构可以不断地优化,持续性变好,使得架构可以适用于当前的场景。所以架构是可以改变的,架构是需要变化的。

那么到底什么是架构呢?

为什么需要软件架构

什么是软件架构

对于软件架构,不同的人有不同的理解,不同的组织有不同的定义。

维基百科是这么给软件架构定义的:

软件架构是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。

软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。面向对象领域中,组件之间的连接通常用接口来实现。

IEEE(1471 2000)中如是说:

架构是指体现在它的组件中的一个系统的基本组织、组织之间的关系、组织与环境的关系及指导其设计和发展的原则

软件开发和盖房子类似。最初,设计出软件的总体架构蓝图,思考各个模块之间的关系,实施一系列相关的架构决策。然后,选择软件开发所需要的一系列技术栈、框架等,讨论关于应用的上线、部署等流程问题。最后,才能进入软件的开发阶段。在开发的过程中,还需要保证软件的质量,才能设计出符合要求的系统。

开发人员需要怎样的软件架构

不以实现为目的的架构,都是无意义的。

我们期望的软件架构,应该是贯穿在它被应用的生命周期里的,应该包含以下的内容:

  • 系统间关系。明确的指出该系统与其他系统之间的关系,是调用关系,还是依赖关系等。
  • 系统内关系。系统内各子系统之间的关系,如前端与后端,以怎样的方式通信,需要怎样的通信机制。
  • 应用内架构。包含应用相关的框架、组件,并清楚的表示出它们之间的关系。
  • 规范和原则。用于指导项目中的开发人员,编写出符合需求的代码,以构建出设计中的架构。

架构的设计

架构设计并非只是一个技术工作,它包含一系列复杂的工作,其范围包含软件工程、开发时间、业务交付等相关领域。

相应步骤如下:

  1. 收集利益相关者的需求。倾听业务人员、项目负责人等相关工作着的需求,进行用户访谈,收集相关的需求。
  2. 与相应的技术人员讨论,了解架构上的潜在限制。
  3. 寻找潜在的可行性技术方案。
  4. 整理出功能列表中的功能性需求和跨功能性需求。
  5. 找出会严重影响开发的风险点。
  6. 和技术委员会、利益相关者反复确认方案(可选)。
  7. 对架构设计进行概念证明。
  8. 细化架构的部分实施细节。
  9. 结合技术和业务,进行需求排期。

对于不同项目来说,上述步骤会有所不同,有的可以直接忽略一些步骤,有的则会包含更多的步骤。在这个过程中,最重要的还是手机相关的架构需求。

收集架构需求

架构并非完全从技术角度来考虑问题。为了寻找技术相关的因素,我们需要进行相关的讨论,手机一系列的相关资料:

  • 了解相关者的利益

    • 产品或者业务负责人,关心是否能够按时上线。

    • 项目经理,根据架构来决定项目计划与项目人选。

    • 架构师、开发人员,关心系统的构建、演进与维护。

    • 业务分析人员,关心如何分配和安排项目的迭代计划。

    • 测试人员,设计合理的测试计划,如对系统集成部分的测试等。

    不同利益相关者达成共识后才能继续往下设计系统。

  • 寻找架构关注点

    在架构设计过程中,各个组织、开发人员、架构师都有自己的关注点:

    • 性能:应用需要达到怎样的性能指标、可以实现多少用户的并发。
    • 安全:应用如何保障用户数据的安全、如何应对客户端的攻击、如何应对服务端的攻击。
    • 平台化:应用是否需要作为一个平台,来承载其他系统。
    • 代码维护:是不是稍有经验的开发人员能快速上手。
    • 用户体验:用户体验是否比其他几个维度更重要。

    在反复确认之后,我们便会得到这些关注点的优先级顺序,这个优先级对后续的工作非常有帮助,可以减少设计返工。

  • 明确跨功能需求

    按功能性来划分,需求可以分为功能性需求和跨功能性需求(又称为非功能性需求)。

    功能性需求定义了一个软件系统或组件的功能,也是一个系统需提供的功能及服务。而跨功能性需求也是需求的一个重要组成部分,它指的是依靠一些条件判断系统运作的情形或其他行,而不是针对系统特定性为的需求。

    它们从源头可分为:

    • 运行质量:即可以在系统运作时观察到的质量,例如安全性、易用性等。
    • 演进质量:它们体现在系统的静态结构中,例如软件可测试性、可维护性、可扩展性、可伸缩性等。

    与性能相关的指标:可用性、可维护性、可变性、容错性、可伸缩性。

    前端应用往往会影响应用的架构,部分相关需求如:浏览器支持范围、移动端设备支持的版本。

  • 罗列技术风险点

    常见风险如下:

    • 技术风险:如果在应用中某一功能的实现比较复杂,或者团队缺乏某一领域的经验,那么在时间的时候可能会影响系统的机构。
    • 第三方系统集成:需要体检进行技术方面的评审,并在上线钱的安排联调。
    • 受限制的线上运行环境:在开发人员和运维人员部门分离的组织里,往往会在内部限制某种语言,如果使用的是其他语言,运维人员可能难以维护,需要培训或者招新的运维。
    • 若是一些需求带来额外的技术膨胀,则会为按时完成应用的开发带来风险。这时,要么努力去说服业务人员修改需求--适当降低交互效果,要么将相应的功能排期靠后。

架构模式

架构风格是描述某一特定应用领域中系统组织方式的管用模式。架构风格便是架构模式的展现形式。

日常开发中常见的架构风格有:

  • **分层风格:**这是最常见的架构风格,它讲系统按照水平切分的方式分成多个层。一个系统由多个层组成,每个层由多个模块组成。每一层为商城提供服务,并使用下层提供的功能。
  • MVC 架构风格:这种风格应用的相当广泛,它强调职责分离,将软件系统分为三个基本部分:模型(Model)、视图(View)、控制器(Controller)。由视图和控制器一起完成用户界面的功能,并设计一套变更机制,来保证用户界面与模型的一致性。它是一种常见的架构风格,在涉及图形用户界面时,往往都有它的身影,如前端应用,移动端应用等。
  • **发布-订阅风格:**这种风格又可以称为基于事件的架构风格,它是一种一对多的关系,一个对象状态发生改变时,所有订阅它的对象都会得到通知。这种架构风格带来的最大好处是,代码上的解耦。发布者不关心事件的订阅者,只需要在适当的时候发布相应的事件即可;而订阅者则依赖于事件,而非事件的发布者。在前端的日常开发中,为了解耦不同的UI组件的依赖,会经常采用这种模式。
  • **管道和过滤器:**这是一种适用于处理数据流的架构模式,它将每个步骤都封装在一个过滤器组件中,数据通过相邻过滤器之间的管道传输。最经典的管道-过滤器架构是UNIX shell的设计。在Unix系统中,使用“|”作为管道符号,当我们需要编写复杂的Shell脚本来处理内容时,便会使用这个符号。它也适用于相关的数据处理场景,如我们在采用Hadoop、Spark等编写数据处理相关的代码时,便会采用这种模式来编写。如前端框架的Angular,也直接内置了管理(Pipe)系统。

架构设计方法

简单介绍一下4+1视图法及TOGAF。

  1. 架构开发方法:4+1视图

    每个项目都会有一张系统架构图,里面包含了系统的主要层级之间的关系,以及一些第三方系统之间的关系。这张图不仅会涉及系统的软件架构部分,还会涉及系统相关的部署内容。我们可以通过开发、部署、流程相关的内容来了解系统的架构,也可以利用Philippe Kruchten提出的“RUP4+1视图方法”来进行相应部分的设计和架构探索,如图:image-20200504201037275

    图中的逻辑视图、流程视图、开发视图、物理师徒和场景的还以如下:

    • 逻辑视图(Logical View):在设计期的模块、接口划分,职责及协作方法等。
    • 流程视图(Process View):在运行期运行的数据同步,如在微前端中的数据流、控制流等。
    • 开发视图(Development View):在开发期的框架、库、技术选型及其对应的编译。
    • 物理视图(Physical View):又称为部署视图。在部署期及持续交付相关的技术决策。
    • 场景(Scenarios):又称为用例视图,它使用一组用例或场景来说明架构。

    可以看出4+1视图法的设计流程如下:

    (1)架构人员根据需求创建相应的逻辑架构,开发人员进行详细设计。

    (2)架构人员和开发人员根据需求设计物理架构,再由开发人员根据物理架构进行对应的详细设计。

    值得注意的是,4+1视图法是依靠软件建模和OO技术来进行架构设计的,尤其在逻辑架构设计方面更符合传统的瀑布模式,需要事先设计好各类接口,才能进入项目的开发阶段。这种烦琐的设计方式,既不适合与互联网应用,又不适合于前端应用。

  2. 架构开发方法:TOGAF

    针对大型的企业架构来说,可以采用TOGAF(The Open Group Architecture Framework,开放组织体系结构框架)的标准化方式来设计企业架构。企业家沟可分为如下4个架构域:

    • 业务架构:定义开发战略、治理方法和关键业务流程。
    • 应用架构:为将要部署的各个应用程序提供蓝图,并展示它们的交互及核心业务流程的关系。
    • 数据架构:描述了一个组织的逻辑、物理数据资产及数据管理资源的架构。
    • 技术架构:定义了支持部署业务、数据和应用程序服务所需的逻辑软件和硬件功能,它包含了IT基础设施、中间件、网络、通信、处理和标准。
  3. 架构开发方法:ADM

    架构开发方法ADM(Architecture Development Method)是以开发架构为目的,为其提供的一种可重复的测试过程。ADM包含了建立体系结构框架,开发架构内容,过渡及管理架构的实现。常用于创建企业级架构,共分为一下8个阶段:

    • 架构愿景:设定项目的范围、约束和期望。
    • 业务架构:开发业务架构,用于支持设定的架构愿景。
    • 信息系统架构:开发信息系统架构,用于支持设定的架构愿景。
    • 技术架构:开发技术架构,用于支持设定的架构愿景。
    • 机会及解决方法:为前几个阶段定义的架构进行初步实施计划和交付工具的识别。
    • 迁移计划:通过最终确定的详细实施和迁移计划,阐述如何从基准体系结构迁移到目标体系结构。
    • 实施治理:准备和发布架构契约,并对实施的架构进行监督,以确保实施体系项目符合架构的要求。
    • 架构变更管理:对架构变更进行持续的监控。

前端架构可以通过层次设计的方式来进行,即由顶至底进行一层一层的技术决策,再由底至顶逐层验证方法的可行性。

生产架构产出物

不论采用哪种架构设计方式,都需要留下相应的架构文档,它们将为团队开发打下基础。在进入开发阶段时,作为一个普通的开发人员希望得到的内容如下:

  • **架构图:**它包含了系统的整体架构,用于显式的告诉开发人员,它们是如何构成整体系统的,以及每个部分之间的关系。同事,显式的表明哪些部分是第三方系统,以及它们与该系统之间的关系。
  • **迭代计划:**按照业务和技术的要求,按时间顺序排列出项目的实施计划。由于其中也包含了上线时间,所以也可以从上线时间开始往前推算出迭代时间。
  • **开发流程:**定义开发人员的工作方式,诸如采用敏捷还是瀑布的开发模式、何种源码方式(主分支、GitFlow或者Feature Branch 工作流),必要时还要提前准备相应的工具和设备。然后,有针对性的对开发流程进行一定程度的裁剪,以真正满足团队的开发。
  • **技术栈及选型:**确定项目中使用的语言、框架、库等相关的技术栈,以及相应的依赖等。
  • **示例代码:**在这些代码中展示架构的风格及相应的设计规范。
  • **测试策略:**明确项目的测试类型,测试流程,以及相应的人员在那些层级进行测试。
  • **部署方式:**定义应用的部署方式及相应的部署方案。
  • **持续集成方案:**描述系统的各个模块之间(如前后端)如何集成,以及采用怎样的时间和频率来集成相关的模块。

有了这些基本流程,开发人员就可以寻找合适的工具,开始搭建项目。反过来,也可以从我们所需要的部分反推出我们所需要的内容。

架构设计原则

不同的人在设计架构时会出现不同的风格,在细节的把握上也会出现特有的风格,这便是架构的设计原则。笔者总结了三个设计原则:

  • 不多也不少:不做多余的设计,也不缺少关键的部分。
  • 演进式:不断的演进以使架构适应当前的环境。
  • 持续性:长期的架构改进比什么都重要。

他们不是真实的架构需求,而是隐藏在背后的设计思想,也是不同人的设计价值观。

不多也不少

一个好的架构设计,内容不多也不少。增加新的元素,觉得有些多余;想删除某个元素,却又找不到更合适的。无论是艺术设计领域,还是计算机领域,原则都是相似且适用的。

设计过多则过度设计,设计过少则设计不足,对于过度设计或者设计不足,往往很难做到真正的平衡和适宜。

演进式

架构都只是适合当前的情况,一谈论到各种需求变化时,会发现架构设计上有各种不足,这并非是架构的问题。架构要不断根据需求演进变化,以满足新的需求。

持续性

架构的持续性原则的意图是,敢于修正架构中的错误部分,再修中过程中尽管可能会带来一些不合适的中转式架构,但是也会很快被纠正过来,具体包含以下几个方面:技能水平的持续改进、应用的持续改进、设计能力的持续提升。

前端架构发展史

最初,前端是没有架构的,因为功能简单的代码没有架构可言。通过操作DOM就能完成的工作,不需要复杂的设计模式和代码管理机制,也就不需要架构来支撑起应用。前端开发的发展历史分为以下几个阶段:

  • 古典时期:有后端渲染出前端HTML,用Table布局,用CSS进行简单的辅助。
  • 动效时期:前端开始编写一些简单的JavaScript脚本来做动画效果。
  • Ajax异步通信时期:2005年,Google在诸多Web应用中使用了异步通信技术,开启了Web前端的一个新时代。

一旦前端应用需要从后端获取数据,就意味着前端应用在运行时动态的渲染内容的,这便是Model(模型)UI层解耦。JQuery能够提供DOM才注意方法和模板引擎等。这时的开发人员需要做下面两件事情:

  • 动态生成HTML。由后端返回前端所需的HTML,再动态替换页面的DOM元素,早起的典型架构如JQuery Mobile,事先在前端写好模板与渲染逻辑,用户的行为触发后台并返回对应的数据,来渲染文件。
  • 模板分离。由后端用API返回前端所需要的JSON数据,再由前端来计算生成这些HTML。前端的模板不在使用HTML,而是使用诸如Backbone,Knockout等等。

与此同时,在 Ryan Lienhart Dahl等人开发了 Node.js 之后,前端的软件工程便不断地改善:

  • 更好的构建工具。诞生了诸如Grunt和 Gulp等构建工具。

  • 包管理。产生了用于前端的包管理工具 Bower和 NPM.

  • 模块管理。也出现了AMD、Common.js等不同的模块管理方案。

随着单页面应用的流行,前后端分离架构也成为行业内的标准实践。由此,前端进入了一个新的时代,要考虑的内容也越来越多:

  • API管理,采用了诸如Swagger的API管理工具,各式的 Mock Server 也成为标准实践。
  • 大前端,由前端来开发跨平台移动应用框架,采用诸如Ionic、React Native、Flutter 等框架。
  • 组件化,前端应用从此由一个个细小的组件结合而成,而不再是一个大的页面组件。

系统变得越来越复杂,架构在前端的作用也变得越来越重要。MVC 满足不了开发人员的需求,于是采用了组件化架构。而组件化+MV*也无法应对大型的前端应用,微前端便又出现在我们的面前,它解决了以下问题:

  • 跨框架。在一个页面上运行,可以同时使用多个前端框架。

  • 应用拆分。将一个复杂的应用拆解为多个微小的应用,类似于微服务。

  • 遗留系统迁移。让旧的前端框架,可以直接嵌入现有的应用运行。

复杂的前端应用发展了这么久,也出现了一系列需要演进的应用-考虑重写、迁移、重构,等等。

前端架构设计:层次设计

从笔者的角度来看,架构设计本身是分层级的,面向不同级别的人时所展示的内容也是不一样的。如我们作为一个架构师、技术人员,在面对同一级别、更高一级别的架构师、技术人员时,说的便是形而上学的东西,如微前端、前后端分离等各种概念。这些概念,对于接触过相关知识的程序员而言很容易理解。而当我们面对经验略微丰富的程序员的时候,说的可就不是:去实现微前端这样的东西,而是需要落实到怎样去做这样的一件事。

不同阶段构成架构的因素是不同的,基于这个思路,架构设计可以分为四个层级:

  • 系统级,即应用在整个系统内的关系,如与后台服务如何通信,与第三方系统加何集成。
  • 应用级,即应用外部的整体架构,如多个应用之间如何共享组件、如何通信等。
  • 模块级,即应用内部的模块架构,如代码的模块化、数据和状态的管理等。
  • 代码级,即从基础设施来保障架构实施。

在设计的时候,既要用自上而下的方式来设计架构,又要用自下而上的方式来完善架构。从演进式设计的角度来看,我们需要在前期设计的时候,对所有系统级架构及部分应用级架构进行技术决策,而其余部分的架构则可以在实施的过程中考虑。

系统内架构

在设计前端架构的时候,首先考虑的是应用在整个系统中的位置-它与系统中的其他子系统是怎样的。这种关系包含了架构和业务上的关系,以及它们之间的协作机制。对于前端应用来说,这一部分的子系统包含了如下两方面内容:

  • 其他前端应用。关注如何与这些应用进行交互、通信等。
  • 对接的后台服务。关注如何与后台服务进行通信,诸如权限、授权、API管理等。

如果是系统间的数据通信(如与后台服务之间的交互),那么只需要规定数据通信数据传递的机制即可。这一类的通信机制,不仅包含了前后端分离架构的API设计,还包含了各类的数据传递。

因此,对于前端与后端的关系,我们所需要考虑的主要因素是前后端分离架构的设计。

前后端分离是指前后端分离如何实施的技术决策。它包含了一系列的决策、用户及安全、API接口管理与设计、API文档管理、Mock Server使用、BFF(服务于前端的后端)、是否需要服务端渲染等。

当在一个系统内时,微前端是一个应用间的架构方案,当在多个应用间时,微前端则是一种系统间的架构方案。

微前端是将多个前端应用以某种形式结合到一起,当系统中存在多个前端应用(或着单个前端应用的模块较大)时,就需要考虑使用微前端的方式来拆分。此外,还需要做一系列的技术决策来支持应用的整合。

然后,我们还需要考虑前端的客户端展现形式。在大前端的背景下,它可能是PC Web应用、移动 Web 应用、混合移动应用(结合Cordova构架)、混合桌面应用(结合 Electron 框架)、原生移动应用(结合 React Native)等,具体选择哪一种技术,取决于我们先前调查的利益相关者的需求。当我们做完上述三个核心的架构决策之后,就需要考虑应用的部署架构了。有的客户端形式可能需要服务端渲染,会在某种程度上影响到前端应用的部署, 但是总的影响并不大,往往只需要通过反向代理的配置就可以完成部署的配置。如果与后 台服务不在一个域,则需要考虑支持跨域请求,或者让后台做一层代理。

应用级架构

应用级架构指的是,单个应用与外部应用的关系,如微服务架构下的多个应用的协作。它可以是一个团队下的多个前端应用,也可以是一个组织内的前端应用。其在组织中所处的位置,也进一步决定了我们所需要设计的架构案。

由于各应用之间需要通过复用代码、共享组件库、统一设计等减少工作量,因此,我们要考虑以下几方面内容。

  • 脚手架:作为一个基础的模块应用,脚手架用于快速生成、搭建前端应用 。他除了包含一个前端项目所需要的要素,还包含着与组织内部相关的规范和模式,如部署模板,构建系统等。团队内的多个应用之间往往会使用同一套构建系统,该构建系统会包含在脚手架中,除了包含一系列的构建脚本,它还可以包含编写好的统一的CLI工具。
  • 模式库,它是一系列可复用代码的合集,如前端组件、通用的工具函数等,其作用是在多个应用之间共享代码,降低修改成本。在设计架构的时候,如果考虑内建相应的UI组件库,就需要考虑结合装饰器模式,将模式库作为一层代理来封装外部的 API,以降低后期的修改成本。模式库还包含了用于多个前端应用通信的数据通信库。
  • 设计系统。它相当于更高级别的UI组件库,在这个层级里,我们关注抽象通用的UI模式,用于在多个系统之间共享设计。与模式库/组件库不同,设计系统偏向于设计人员的模式,而非开发人员的视角。

模块级架构

模块级架构是深入单个应用内部、更细致地设计应用内部的架构,它所涉及的内容是我们在日常开发中经常接触的。我们所要做的是,制定一些规范或者更细致的架构设计。这部分内容会在我们开始业务编码之前进行设计。

在敏捷软件开发中,它被称为迭代 0/Sprint 0/Iteration 0,其相关的内容有以下两个方面:

  • 模块化。它包含了 CSS、JavaScript、HTML/模板的模块化。对于JavaScript或者模板而言,其模块化的设计受框架的影响比较深。对于CSS来说,我们也需要设计一个合理的方式来进行管理,既需要考虑全局样式以用于样式复用、局部样式以用于隔离变化、通用变量以方便修改,又需要考虑相应的工具来辅助设计。此外,还需要定义相应的CSS、JavaScript、模块的代码组织方式。

  • 组件化。它主要考虑的是,在应用内如何对组件进行封闭,以及相应的原则和粒度。

代码级:规范和原则

当我们真正编码的时候,考虑的架构因素是更低层级的内容,这部分架构设计被称为代码级的架构设计,它关注于实践过程中的相关规范和原则。这部分内容相当多并且烦琐,包含但不限于下述内容:

  • 开发流程。它包含了开发一个功能所需要的完整流程-从源码管理方式、代码合并方式、代码提交信息规范、代码规范自动化,到测试编写等一系列的过程。编写开发流程的目的是,保证编写、创建出来的代码能够符合项目的要求。
  • 代码质量及改善。在实施过程中不仅要注重代码整洁,还要注重 TDD(测试驱动开发)等相关的实践,并且遵守 SOLID 原则,以保证代码的质量。此外,还需要制定代码的测试策略,测试的目的并非减少 bug,而是用测试来保证现有的功能是正确的。
  • 规范而非默契。在整个架构中,我们会更关注规范化。小的团队可以依赖于默契,大的团队则需要规范。它需要我们关注几个方面:代码风格的统一,如统一化编辑器、IDE等的配置、使用几个空格;代码的命名;如何保持一致性等。
  • 此外,在日常的开发中,还需要注重代码的可维护性-简单的代码更容易被读懂和维护。