当前位置: 首页 > 科技观察

微内核架构在大型前端系统中的应用

时间:2023-03-17 15:11:49 科技观察

只讨论架构,不讨论框架1.术语解释由一组尽可能减少数量的软件程序组成,他们负责提供和实现操作系统所需的各种功能。机制和作用。这些最基本的机制包括低级地址空间管理、线程管理和进程间通信。2、设计理念将系统的实现与系统的基本运行规则区分开来。它的实现方式是将核心功能模块化,分成几个独立的进程,分别运行。这些过程称为服务。所有服务进程都运行在不同的地址空间中。使服务独立可以降低系统间的耦合度,便于实现和调试,提高可移植性。它可以避免单个组件的故障而导致整个系统崩溃。内核只需要重启这个组件,不会影响其他服务器的功能,增加了系统的稳定性。同时,业务功能可以根据需要替换或增加一些服务流程,使功能更加灵活。在代码量方面,由于功能简化,核心系统总体上比集成系统使用的代码更少。更少的代码意味着更少的隐藏程序错误。3、具体应用微内核架构在使用时主要考虑两个方面:“核心系统”和“插件模块”。应用逻辑被划分为独立的“核心系统”和“插件模块”,提供了良好的扩展性和灵活性,应用的新特性和基础业务逻辑也会隔离。1.核心系统核心系统通常是一个可以独立运行的最小模块,操作系统(WindowsNT、MacOSX)就是这样实现的。从业务应用的角度来看,核心系统为那些特定的场景、规则、复杂的条件判断提供通用的业务逻辑,而插件模块则提供更具体的业务逻辑。可以添加或扩展核心系统以实现生成额外业务逻辑的能力。2.插件模块插件模块通常是一个独立的组件,专门用于附加功能。通常,插件模块之间没有依赖关系。当然,你也可以创建一个依赖于其他插件模块的插件,但不管怎样,让插件模块之间能够相互通信而不产生依赖关系是一个非常重要的问题。3.获取插件模块和判断可用性核心系统需要知道每个插件的可用性,知道如何获取。一种常见的实现方法是使用一组注册表。注册表包括每个插件的基本信息,包括名称、数据规范、远程访问协议(取决于插件模块如何连接到核心系统)和其他自定义数据。例如百度网盘上传文件的上传插件,提供了插件名称、数据规范(输入、输出数据)、数据格式(json、xml)。如果插件是异步加载的,会有一个特定的RemoteHTTP访问协议地址。4.连接核心系统插件模块可以通过多种方式连接到核心系统,包括OSGI(开放服务网关倡议)、消息机制、web服务、点对点绑定(对象实例化,两者依赖注射)。采用哪种方式主要看具体的应用场景和特殊需求(单机部署、分布式部署)。微内核架构默认不要求具体的实现方式,但必须保证插件模块之间不存在依赖关系。5.通讯规范插件模块与核心系统的通讯规范分为标准规范和定制规范。自定义规范通常意味着插件模块是由第三方服务开发的。在这种情况下,需要在自定义规范和标准规范之间提供一个Adapter,这样核心系统就不需要关心每个插件模块的具体实现。在设计标准规范之前制定版本控制策略很重要。6.事件模式核心系统提供了多种事件模式,主要包括常用的点对点模式和发布订阅模式。同时,事件的类型分为全局(系统级)事件、系统内部事件和插件模块内部事件。由于点对点模式下发送方和接收方之间没有依赖关系,一条消息只对应一个接收方,因此可以用来广播全局(系统级)事件,比如调用一个插件模块.在发布-订阅模式下,订阅者和发布者之间存在时间依赖,可用于系统内部事件和插件模块内部事件。此外,核心模块还可以通过发布-订阅的方式发布一些属于业务基本运行规则的事件。7.接口设计插件模块注册到核心系统后,可以通过系统级事件调用具体的插件模块。这时,核心模块需要提供一个属于基本操作规则的接口,供插件模块使用。同样,插件模块也必须根据通信规范提供操作入口(类似java的Main方法)和数据规范(参数格式、返回数据格式)。),使插件模块能够在核心系统上正常运行。插件模块独立于核心系统,但根据具体需求(提供简单的数据服务,处理系统数据和信息),可能需要运行核心模块的系统服务来做一些定制化的功能。这时,核心系统需要提供一个上下文对象(Context),插件模块只能通过这个上下文对象与外界进行交互。上下文对象提供基本操作(调用其他插件模块、调用系统服务、获取系统信息)的API和事件。4.在前端系统中使用把前端系统当作一个操作系统,把基本业务操作的业务逻辑抽象成一个可以独立运行的系统内核,把不属于基本的业务逻辑业务操作作为应用程序完成安装、卸载、禁用、调用、启动等功能。在功能越来越多、依赖越来越多的大型前端系统中,如果项目前期没有很好地考虑后期兼容性的灵活性、扩展性和弹性,很容易项目很难维护或没有人想碰它。一个尴尬的场面,所以最初的设计很重要。现在的大型前端单页面系统都是采用按照业务划分的独立组件,解耦复用,最后通过组件进行堆叠、编译、上线。这样,虽然良好可靠的组件设计考虑了系统的可扩展性、灵活性和弹性,但整个系统仍然紧紧地捆绑在一起,并没有很好地按照基础业务和附加业务进行拆分。当然,很多优秀的前端工程师也从这方面进行了考虑,提出了微前端的概念。不过,微前端还是一个比较新的技术概念,并没有被很多大型前端系统实践。微内核架构在操作系统和很多产品的后端服务和前端应用中经历了很多实践。1.核心模块和系统服务的定义上面提到的核心模块是一个可以独立运行的最小模块,包含了系统的基本运行规则。在没有任何插件模块的情况下,它仍然可以正常运行并处理基本的业务逻辑。因此,在大型前端系统中,将基础页面和基础功能分开封装,形成一个最小的模块,称为核心系统。并且这个核心系统可以通过包(NPM、bower、bundle、jschunk)在多个系统之间复用。同时,将那些与业务相关的操作,按照类型和场景,封装成多个系统服务,挂载(依赖注入)到核心系统中,称为系统服务。需要注意的是,核心系统可以运行系统服务,但系统服务不能运行核心系统。此外,核心系统根据特定的模块规范(AMD、CMD、CommonJS、ESM、SystemJS、UMD)对外暴露交互API和事件,称为标准接口。后续开发插件模块必须严格按照标准接口进行。2.插件模块定义插件模块是独立于核心系统之外,专业处理不属于系统基本操作的服务,如上传、下载、分享、网盘中的其他功能。每个插件模块都必须按照定义的标准接口和通信规范进行开发,并且每个插件模块都是相互独立的,所以对每个插件的实现细节没有太多的要求。比如A插件模块使用React开发,B插件模块使用Vue开发,C模块使用jQuery开发。每个插件模块都应该提供一个包含插件模块签名信息(Mainfest)的JSON文件。签名信息包括插件名称、数据规范、依赖、远程访问地址(异步加载的js下载地址)等自定义字段。在前端加载核心系统时注册Mainfest文件,完成核心系统与插件模块的连接。每个插件模块都应该提供一个运行入口,名字统一,比如start方法。插件的生命周期事件也可以按照标准接口提供,方便更细粒度的控制。3.注册和调用每个插件都提供了自己的Mainfest签名文件和可执行文件(JS文件,CSS文件)。因此,当服务端收到浏览器请求时,可以将需要的插件Manifest合并成一个大的JSON结构,然后返回给浏览器。浏览器收到后,执行核心系统并注册Manfiest信息,然后启动。在注册过程中,可以根据需要完成开机(默认执行)、预加载、后台运行等不同类型的操作。在业务逻辑和插件内部逻辑中可能需要调用其他插件模块。由于插件模块互不依赖,独立于核心系统,不能直接调用。但由于registry的存在和点对点的事件模式,可以通过core对外暴露的API,传入插件名、所属组、插件模块ID等信息来调用系统。调用前先判断插件是否被注册、加载(同步加载、异步加载)、是否单例互斥、参数信息和数据格式,确保能够正确调用。当插件运行过程中出现异常时,通过系统级事件通知核心模块。核心模块根据签名信息中的标识选择重启或关闭插件模块。4、多入口管理在复杂的前端系统中,同一个功能可能有多个入口。例如,通过点击不同位置的按钮调用上传、下载、分享等功能。通常,具体的功能(插件模块)与插件模块入口的UI呈现是隔离的。首先将基本页面结构按需求进行分块,将其分为不同的功能块,如菜单栏、右键菜单、列表项、右侧区域、左侧区域,并为这些区域定义唯一的名称和ID.在需要展示入口的插件模块的Manifest中,标识入口的区域和展示方式(按钮、图片、引导块、菜单项、下拉菜单)。核心系统在注册中心注册后,解析需要显示条目的字段,下发给渲染插件模块条目的系统服务。这样就通过配置完成了对多个表项的管理。当后续需求发生变化和修改时,只需要更改Manifest文件即可,进一步提高了系统的可扩展性、灵活性和弹性。5、技术选型架构独立于框架和类库。微内核架构的核心是将业务的基本操作与专门处理附加功能的操作隔离开来,提高系统的可扩展性、灵活性和弹性。因此,我们在选择技术时需要考虑三个方面:核心系统、系统服务、插件模块。核心系统通常包括一个项目所需的基本功能,包括基本的展示页面、交互操作、业务处理等,代码量通常很小;系统服务提供业务处理的通用功能,如列表操作、弹出框、提示、异步接口处理等,通常将系统的通用需求抽象到这一层;所以这两方面都可以通过webpack工具使用目前常见的react或者vue进行标准化开发,但是如何将核心系统的API和事件暴露给外部调用插件模块是一个很重要的问题。插件模块更倾向于专门处理附加功能的lib库,所以推荐使用rollup或者webpack的lib模式进行开发打包,产生一个“干净”的bundle(也可以发布到NPM独立发布和维护)。需要注意的是,如果按照定义的标准规范开发bundle,则可以运行在任何微内核架构下,实现跨系统能力。就像一个按照X86规范编写的程序,可以运行在任何X86架构的系统上。调用插件模块时如何异步加载插件模块包?一、插件模块开发阶段方案一:源代码插件模块的代码放在一个根目录下,通过源代码开发编译。每次改动后,通过rollup或者webpack生成一个bundle和Manifest文件,然后在线更新。该模式下,插件模块的代码更新后,对应的Manifest文件也会更新,所以加载到插件模块的核心系统也会更新,基本业务无需操作逻辑。优点:无需更新上线基础业务代码。缺点:没有版本号管理功能,测试不方便。方案二:将npminstall插件模块发布到github、gitlab等其他托管平台,通过npm安装到基础业务逻辑中。插件模块每次改动后需要重新发布到托管平台,如果业务逻辑中需要更新版本号,需要重新执行npminstallxxx,然后重新编译业务代码上线。更新插件模块后,不需要像方案一那样启动插件模块,而是更新业务逻辑的依赖,安装最新版本的插件模块。优点:可以通过版本号加载不同阶段的插件模块,方便测试。缺点:更改后需要重新安装插件模块,依赖该插件模块的业务逻辑需要重新编译启动。回归的代价很高。除了返回插件模块外,还必须返回其他基本业务逻辑(当然也可以像方案一那样做,但是这样就放弃了npm->版本号管理的最大优势)。2、获取插件模块的Manifest签名信息。解决方案1:服务器将其呈现为HTML。服务器端收到浏览器的页面请求后,对页面需要的插件模块的Manifest签名文件进行Merge操作,然后统一输出到HTML中。并返回到浏览器。方案二:通过script标签的async和defer函数或者AJAX从服务端异步获取Merge后设置的Manifest签名信息。3、远程访问协议核心系统在调用插件模块时,可以通过插件声明的远程访问协议的HTTP地址异步加载。方案一:Manifest签名文件将插件模块的远程访问协议放在Manifest签名信息中,比如上传插件模块的签名。例:{//插件名称"name":"upload",//分组"group":"com.xxx.xxx",//预加载插件模块资源"preload":true,//数据规范,所需输入参数"arguments":{//核心系统提供的Context对象"ctx":{"type":"Object","re??quired":true},//要上传的文件信息"file":{"type":"Object","re??quired":false}},//远程访问协议"entrance":"http://www.a.com/static/plugin-bundles/upload-0.0.1.min.js"}方案二:异步接口+import()这种方案是系统插件模块的远程访问协议没有放在插件模块的Manifest中,通过异步方式额外请求远程访问协议界面。然后通过webpack提供的require.ensure()或者esm的import()加载插件资源。//ctx是核心系统上下文对象ctx.loadPlugInAdapter=(pluginName,group)=>{//远程访问协议,通过接口请求上传插件模块fetchEntrance(pluginName,group).then(url=>{//核心系统执行插件模块ctx.invoke(pluginName,url);});}//调用插件模块ctx.loadPlugInAdapter('upload','com.xxx.xxx');最后,架构和框架是独立的,这篇文章只是提出一种架构思路,而这种架构也应用在了百度的一款复杂的前端产品中,用户量很大。基于这套灵活的架构,结合Vue/React的现代开发理念,可以很好的完成高复杂度的前端系统。我希望这篇文章能为您提供除微前端之外的另一种构建高弹性前端系统的思考方式。