软件规划中的一个根本问题是可扩展性问题。处理可扩展性问题的一个根本战略是将新的改动要素看作是一个新的维度,然后调查这个维度与已有维度之间的彼此效果联系。

例如,现在针对Order方针编写好了一个OrderProcess处理逻辑,假如作为SAAS软件发布,则需求增加租户维度。最简略的状况下,租户只是是引进数据库层面的过滤字段,
即租户维度相对独立,它的引进不影响详细的事务处理逻辑(租户相关的逻辑独立于特定的事务处理进程,能够在存储层被一致界说并解决)。

可是更杂乱一些的扩展性要求是每个租户能够有自己定制的事务逻辑,则此刻租户维度无法坚持独立性,必定需求与其他事务技能维度产生彼此效果。本文将介绍一个启发式的观念,
它将相似租户扩展这一类具有普遍性的可扩展性问题类比于张量空间经过张量积所完结的扩张进程,并结合可逆核算理论,为这类可扩展性问题供给一个一致的技能解决计划。

一. 线性体系与向量空间

数学中最简略的一类体系是线性体系,它满意线性叠加规矩

f(1v1+2v2)=1f(v1)+2f(v2)f(\lambda_1 v_1 + \lambda_2 v_2) = \lambda_1 f(v_1) + \lambda_2 f(v_2)

咱们知道,任何一个向量都能够分化为基向量的线性组合

v=∑iiei\mathbf v = \sum_i \lambda_i \mathbf e_i

因而,效果于向量空间上的线性函数,其结构本质上是非常简略的,它彻底由函数在基向量上的值来确定。

f(v)=∑iif(ei)f(\mathbf v) = \sum_i \lambda_i f(\mathbf e_i)

只需知道了函数f在全部基向量上的值f(ei)f(\mathbf e_i),咱们就能够直接核算出函数f在ei\mathbf e_i所张成的向量空间中的恣意向量处的值。

依照数学的精力,假如一个数学性质很好,咱们就专门以该性质为前提来界说所需求研讨的数学方针(数学性质界说了数学方针,而不是数学方针具有某种数学性质)。那么体现在软件结构规划范畴,假如咱们主动要求一个结构规划满意线性叠加规矩,那它的规划应该是什么样子?

首要咱们需求从不那么数学的视点从头审视一下线性体系的含义。

  1. f(v)f(\mathbf v) 能够看作是在一个具有杂乱结构的参数方针上履行某种操作。

  2. v=∑i易变的参数⋅标识性的参数\mathbf v = \sum_i 易变的参数\cdot 标识性的参数。有些参数是具有特别标识效果的相对固化的参数,而其他参数是每次恳求都产生改动的易变的参数。

  3. f先效果于标识性的参数(这一效果成果能够事先确定)得到一个核算成果,然后再把这个核算成果和其他参数进行结合运算

举个详细的比如,比如前台提交恳求,需求触发后台的一组方针上的操作。

request={obj1:data1,obj2:data2,…}request = \{ obj1:data1, obj2: data2, … \}

整理成向量方法

request=data1∗obj1+data2∗obj2+…request = data1* \mathbf {obj1} + data2* \mathbf {obj2} + …

当咱们研讨全部或许的恳求时,咱们会发现全部恳求构成一个向量空间,每个objName对应向量空间中的一个基向量

后端结构的处理逻辑对应于

process(request)=data1∗route(obj1)+data2∗route(obj2)+…=route(obj1).handle(data1)+route(obj2).handle(data2)+…\begin{aligned}
process(request) &= data1* route(\mathbf {obj1}) + data2* route(\mathbf {obj2}) + …\\
&= route(\mathbf {obj1}).handle(data1) + route(\mathbf {obj2}).handle(data2) + …
\end{aligned}

结构越过易变的参数data,先效果于方针名参数上,依据方针名路由到某个处理函数,然后再调用该处理函数,传入data参数。

这儿咱们需求注意到 if(ei)\lambda_i f(\mathbf e_i)本质上是 ⟨i,f(ei)⟩\langle \lambda_i, f(\mathbf e_i)\rangle ,即参数与f(ei)f(\mathbf e_i)的结兼并不一定是简略的数值乘法,而能够被扩展为某种内积运算的成果,在软件代码层面,它就体现为函数调用。

二. 张量积和张量空间

在数学中,一个根本问题是怎么从一些较小的、较简略的数学结构动身,主动生成更大的、更杂乱的数学结构,而张量积(Tensor Product)的概念正是这种主动化的结构方法的一种天然成果(这儿所谓的天然性在范畴论中获得了准确的数学界说)。

首要,咱们来看一下线性函数的推行:多重线性函数。

f(1u1+2u2,v)=1f(u1,v)+2f(u2,v)f(u,1v1+2v2)=1f(u,v1)+2f(u,v2)f(\lambda_1 u_1+\lambda_2 u_2,v) = \lambda_1 f(u_1,v) + \lambda_2 f(u_2,v) \\
f(u,\beta_1 v_1+ \beta_2 v_2) = \beta_1 f(u,v_1) + \beta_2 f(u,v_2)

效果于向量空间上的线性函数能够看作一个单参数函数,它接纳一个向量,产生一个值。而相似于单参数函数向多参数函数的推行,多重线性函数具有多个参数,每个参数都对应一个向量空间(能够看作是一个独立的改动维度),当固定调查某个参数时(例如固定参数u,调查参数v或者固定参数v,调查参数u),它都满意线性叠加规矩。相似于线性函数,多重线性函数的值相同由它在基向量上的值所决定

f(∑iiui,∑jjvj)=∑ijijf(ui,vj)f(\sum_i \lambda_i \mathbf u_i,\sum_j \beta_j \mathbf v_j)=
\sum_{ij} \lambda_i \beta_j f(\mathbf u_i,\mathbf v_j)

f(ui,vj)f(\mathbf u_i,\mathbf v_j)实践上等价于传入一个tuple,即

f(ui,vj)≅f(tuple(ui,vj))≅f(ui⊗vj)f(\mathbf u_i, \mathbf v_j)\cong f(tuple(\mathbf u_i,\mathbf v_j)) \cong f(\mathbf u_i\otimes \mathbf v_j )

即咱们能够忘记f是一个多参数的函数,而把它看作是一个接纳了杂乱参数方法 ui⊗vj\mathbf u_i \otimes \mathbf v_j的单参数的函数。回到最初的多重线性函数 f(u,v)f(\mathbf u,\mathbf v),咱们现在能够在新的视角下把它看作是 一个新的向量空间上的线性函数

f(u⊗v)=∑ijijf(ui⊗vj)f(\mathbf u\otimes \mathbf v)=\sum _{ij} \lambda_i \beta_j f(\mathbf u_i \otimes \mathbf v_j)
u⊗v=(∑iiui)⊗(∑jjvj)=∑ijijui⊗vj\mathbf u \otimes \mathbf v = (\sum_i \mathbf \lambda_i \mathbf u_i)
\otimes (\sum_j \beta _j \mathbf v_j)
= \sum _{ij} \lambda_i \beta_j \mathbf u_i \otimes \mathbf v_j

f(u,v)f(\mathbf u,\mathbf v)f(u⊗v)f(\mathbf u\otimes \mathbf v)中的f其实并不是同一个函数,只是具有某种等价性,这儿把它们的符号都记为f罢了。

u⊗v\mathbf u \otimes \mathbf v被称作是向量u\mathbf u和向量v\mathbf v的张量积,它能够被看作是一个新的向量空间中的向量,这个空间便是所谓的张量空间,它的基是 ui⊗vj\mathbf u_i \otimes \mathbf v_j

假如u∈U\mathbf u \in U 是m维向量空间,而v∈V\mathbf v \in V是n维向量空间,则张量空间U⊗VU\otimes V包含了全部形如∑iTijui⊗vj\sum _i T_{ij} \mathbf u_i \otimes \mathbf v_j的向量,它对应于一个mnm\times n维的向量空间(它也被称为是UUVV的张量积空间)。

U⊗VU\otimes V是由全部形如u⊗v\mathbf u\otimes \mathbf v这样的张量积所张成的空间,这儿的张成指的是线性张成,即这些向量的全部线性组合所构成的调集。这个空间中的元素比单纯的u⊗v\mathbf u \otimes \mathbf v这种方法的向量要多,即不是全部张量空间中的向量都能写成u⊗v\mathbf u \otimes \mathbf v的方法。例如

u1⊗v1+4u1⊗v2+3u2⊗v1+6u2⊗v2=(2u1+3u2)⊗(v1+2v2)=u⊗v\begin{aligned}
\mathbf u_1 \otimes \mathbf v_1 + 4 \mathbf u_1 \otimes \mathbf v_2
+ 3 \mathbf u_2 \otimes \mathbf v_1
+ 6 \mathbf u_2 \otimes \mathbf v_2
&= (2\mathbf u_1 + 3 \mathbf u_2)\otimes (\mathbf v_1
+ 2 \mathbf v_2) \\
&=
\mathbf u \otimes \mathbf v
\end{aligned}

可是 2u1⊗v1+3u2⊗v22 \mathbf u_1 \otimes \mathbf v_1 + 3 \mathbf u_2 \otimes \mathbf v_2无法被分化为u⊗v\mathbf u \otimes \mathbf v这种方法,只能坚持线性组合的方法。

在物理上,这对应于所谓的量子纠缠态。

张量积是从简略结构动身结构杂乱结构的一种免费的战略(Free),这儿的免费(在范畴论中具有严厉的数学意义)指的是这个结构进程没有添加任何新的运算规矩,便是从两个调集中各取一个组成一对放在那里罢了。

本质上u⊗v\mathbf u \otimes \mathbf v u\mathbf uv\mathbf v并没有产生任何直接的彼此效果,v\mathbf vu\mathbf u的影响仅在外部函数ff效果到u⊗v\mathbf u\otimes \mathbf v上才会展示。即当 f(u⊗v)≠f(u)f(\mathbf u \otimes \mathbf v) \ne f(\mathbf u)的时分,咱们才会发现v\mathbf v的存在会影响到f效果到u\mathbf u上的成果。

借助于张量积的概念,能够以为多重线性函数等价于张量空间上的普通线性函数,当然,这种说法是很不谨慎的。略微严厉一点的说法是:

对于恣意的(每一个)多重线性函数 :UVW…→X\phi: U\times V\times W …\rightarrow X, 都对应存在一个仅有的张量空间U⊗V⊗W…U\otimes V\otimes W…上的线性函数 \psi, 使得 (u,v,w,…)=(u⊗v⊗w…)\phi(\mathbf u, \mathbf v,\mathbf w,…) = \psi(\mathbf u \otimes \mathbf v\otimes \mathbf w…)

或者说任何效果于向量空间的积UVW…U\times V\times W…上的多重线性函数,都能够被分化为一个两步的映射进程,即先映射到张量积,然后再运用张量空间上的线性函数。

在上一节中,咱们介绍了线性体系和向量空间的概念,指出软件结构能够模拟线性体系的效果进程,结合本节介绍的张量积的概念,咱们很容易得到一个通用的可扩展性规划计划:从接纳向量参数扩展到接纳张量参数,不断增加的可变性需求能够经过张量积来吸收。例如,

process(request)=data∗route(objName⊗tenantId)process(request) = data * route(\mathbf {objName} \otimes \mathbf {tenantId})

增加租户概念或许导致对体系中全部事务方针的处理逻辑都产生改动,可是在结构层面咱们只需求对route函数进行增强,答应它接纳objName和tenantId所组成的张量积,然后动态加载对应的处理函数即可。

假如再仔细思考一下这儿的处理逻辑,咱们会发现假如把软件结构完结为一个线性体系,那么它的中心其实是一个以张量积为参数的Loader函数

在软件体系中,Loader函数的概念无处不在,但它的效果其实并没有得到充沛的认知。回忆一下NodeJs的状况,全部被调用的库函数在方法上都是经过require(path)函数装载得到的,即咱们调用函数 f(a)的时分,本质上履行的是require(“f”).call(null, a)。假如咱们对require函数进行增强,答应它依据更多的标识性参数进行动态加载,显然咱们能够完结函数等级的可扩展规划。Webpack和Vite中所运用的HMR模块热更新机制,能够被了解为一种Reactive的Loader,它监控依靠文件的改动,然后从头打包、加载并替换当时正在运用的函数指针。

可逆核算理论为Loader函数供给了新的理论层面的诠释,并带来了一个一致的、通用的技能完结计划。在后面的内容中,我将介绍在Nop Platform2.0(可逆核算的开源完结)中所运用的技能计划的概略和其根本原理。

三. Everything is Loader

程序员问函数:汝从哪里来,欲往哪里去?

函数答曰:生于Loader,归于data

函数式编程的箴言是全部都是函数,everything is function。可是考虑到可扩展性,这个function就不或许是变化不居的,在不同的场景下,咱们最终实践运用的必定是不同的函数。假如程序的根本结构是 f(data),咱们能够用一种体系化的方法将其改造为

loader(“f”)(data)。很多结构、插件的规划都能够从这个视点去审视。

  • Ioc容器:

    buildBeanContainer(beansFile).getBean(beanName, beanScope).methodA(data)

    Loader(beansFile⊗beanName⊗beanScope⊗methodName)Loader(beansFile\otimes beanName\otimes beanScope \otimes methodName)
  • 插件体系

    serviceLoader(extensionPoint).methodA(data)

    Loader(extensionPoint⊗methodName)Loader(extensionPoint \otimes methodName)
  • 工作流:

    getWorkflow(wfName).getStep(stepName).getAction(actionName).invoke(data)

    Loader(wfName⊗stepName⊗actionName)Loader(wfName\otimes stepName \otimes actionName)

当咱们在体系的各个层面都辨认出相似的Loader结构之后,一个有趣的问题是:这些Loader内涵的一致性到底有多高?它们之间能不能复用代码?工作流引擎、IoC引擎、报表引擎、ORM引擎…,各式各样的引擎都需求加载自身特定的模型,它们现在大多是各自为战,能否笼统出一个体系级的、一致的Loader来担任模型加载?假如能够,那么详细有哪些公共逻辑能够在这个一致的Loader中完结?

低代码渠道的规划方针是完结代码逻辑的模型化,而模型以序列化的方法保存时就形成模型文件。可视化规划的输入输出是模型文件,所以其实可视化只是模型化的一个附带收益。一个一致的低代码渠道最根本的一个工作应该是 一致管理全部模型,完结全部模型的资源化。Loader机制必定是这样的低代码渠道中的一个中心组件。

咱们来看一个日常开发中常见的函数

JsonUtils.readJsonObject(String classPath, Class beanClass)

这是一个通用的Java装备方针加载函数,它读取classpath下的一个json文件,并经过JSON反序列化机制把它转化为指定类型的java方针,然后在编程中咱们就能够直接运用这个方针了。而假如装备文件格局过错,比如说字段名写错了,或者数据格局错了,则在类型转化阶段能够被检测出来。假如装备了@Max,@NotEmpty这样的一些验证器注解,咱们甚至能够在反序列化的时分进行一些事务相关的校验。显而易见,各类模型文件的加载和解析其实都能够看作是这一函数的变种。以工作流模型加载为例,

workflowModel = workflowLoader.getWorkflow(wfName);

相比于较为原始的json解析,工作流模型的加载器一般具有以下增强:

  1. 或许从数据库中加载,而不限于从class path下的某个文件加载

  2. 模型文件格局或许选用xml格局,而不限所以json格局

  3. 模型文件中能够装备可履行的脚本代码,而不限所以装备string/boolean/number等少量原始类型的数据项。

  4. 模型文件的格局校验愈加严厉,比如检查特点值在枚举项范围之内,特点值满意特定的格局要求等。

Nop Platform 2.0是可逆核算理论的一个开源完结,它能够看作是支撑范畴特定言语(DSL)开发的一个低代码渠道。在Nop渠道中,界说了一致的模型加载器

interface IResourceComponentManager{
    IComponentModel loadComponent(String componentPath);
}
  1. 经过模型文件的后缀名能够辨认模型类型,因而不需求传入componentClass这种类型信息

  2. 模型文件中经过x:schema=”xxx.xdef”来引进模型所需求满意的schema界说文件,然后完结比java类型束缚更严密的格局和语义校验。

  3. 经过增加expr等字段类型,答应在模型文件中直接界说可履行代码块,并主动解析为可履行函数方针

  4. 经过虚拟文件体系,支撑模型文件的多种存储方法。例如能够规定一种途径格局,指向存储在数据库中的模型文件。

  5. 加载器主动收集模型解析进程中的依靠联系,依据依靠联系主动更新模型解析缓存。

  6. 假如装备一个FileWatcher,能够完结当模型依靠产生改动时,主动推送更新后的模型。

  7. 经过DeltaMerger和XDslExtender完结模型的差量分化和组装。在第五节中会更详细的介绍这一点(它也是Nop渠道与其他渠道技能明显的差异之处)。

在Nop渠道中,全部的模型文件都是经过一致的模型加载器加载的,一起,全部的模型方针也都是经过元模型(Meta Model)界说主动生成的。在这种状况下,回看上面的工作流模型的处理进程

getWorkflow(wfName).getStep(stepName).getAction(actionName).invoke(data)

getWorkflow经过一致的组件模型加载器担任完结,不需求特别编写,一起getStep/getAction等方法也经过元模型界说主动生成,相同不需求特别编写。因而,整个Loader的完结能够说是彻底主动化的

Loader(wfName⊗stepName⊗actionName)Loader(wfName\otimes stepName \otimes actionName)

换一个视点去了解,Loader的参数能够看作是一个多维坐标(全部可用于仅有定位的信息都是坐标):每个wfName对应一个虚拟文件途径path,而path是在虚拟文件体系中定位所需的坐标参数,一起stepName/actionName等是在模型文件内部进行仅有定位所需的坐标参数。Loader接纳一个坐标,回来一个值,所以它也能够被看作是界说了一个坐标系。

可逆核算理论在某种意义上正是要建立并保护这样一个坐标体系,并研讨在这个坐标体系中模型方针的演化和发展。

四. Loader as Multiple Dispatch

函数代表了某种静态化的核算(代码自身是确定性的),而Loader供给了一种核算机制,它的核算成果是回来的函数,所以Loader是一种高阶函数。假如Loader不是简略的依据参数定位到某个现已存在的代码块,而是能够依据传入的参数动态的生成对应的函数内容,则Loader能够作为元编程机制的一种切入点。

在程序言语理论中,有一种言语内置的元编程机制称为多重派发(Multiple Dispatch),它在Julia言语中得到了广泛的运用。多重派发与这儿所界说的Loader机制有诸多相似之处,实践上Loader能够看作是对多重派发的一种逾越类型体系的扩展。

调查一个函数调用f(a,b),假如是选用面向方针言语来完结,咱们将选择把第一个参数a完结为类型A的方针,而函数f是类型A上界说的一个成员函数,b为传给函数f的一个参数。面向方针的调用方法a.f(b)是所谓单重派发的,即依据函数的第一个参数a(this指针)的类型,动态的查询类型A的虚拟函数表,确定所需求调用的详细函数。也便是说,

在面向方针的观念下 f::A->(B->C)

a.f(b)在完结层面对应于一个函数 f(a,b),a为隐式传递的this指针

而所谓的多重派发,指的是调用函数时,依据全部参数的运行时的类型,选择一个"最适合"的完结函数来进行调用,即

在多重派发的观念下 f:: A x B -> C, AxB为A和B构成的元组

Julia言语能够在编译期依据调用函数时给定的参数的类型,动态的生成一个特化的代码版本,然后优化程序功能。例如 f(int,int)和f(int, double)在Julia言语中或许会生成两个不同的二进制代码版本。

假如选用向量空间的观念,咱们能够把不同的类型看作是不同的基向量,例如 3实践上对应于 3 int , 而”a”实践上对应于 “a” string(类比于 iei\lambda_i \mathbf e_i),不同类型的值原则上是彼此分离的,类型不匹配的时分不答应产生彼此联系(不考虑类型主动转化的状况),恰如不同的基向量之间彼此独立。在这个意义上,多重派发 f(3, “a”) 能够被了解为 [3,”a”]⋅Loader(int⊗string)[3,”a”]\cdot Loader(int \otimes string)

类型信息是在编译期附加到数据之上的一种描绘性信息,本质上它并没有什么特异之处。在这个意义上,Loader能够看作是一种更通用的、效果于恣意基向量组成的张量积上的一种多重派发。

五. Loader as Generator

一个通用的模型加载器能够看作是具有如下类型界说:

    Loader :: Path -> Model

对于一种通用规划,咱们需求意识到一件工作,所谓的代码编写并不只是是为了应对眼前的需求,而是需求一起考虑到未来的需求改动,需求考虑到体系在时空中的演化。 换句话说,编程所面向的不是当时的、仅有的国际,而是全部或许的国际。在方法上,咱们能够引进一个Possible算子来描绘这件工作。

    Loader :: Possible Path -> Possible Model
    Possible Path = stdPath + deltaPath

stdPath指模型文件所对应的规范途径,而deltaPath指对已有的模型文件进行定制时所运用的差量定制途径。举个比如,在base产品中咱们内置了一个事务处理流程main.wf.xml,在针对客户A进行定制时,咱们需求运用一个不同的处理流程,可是咱们并不想修正base产品中的代码。此刻,咱们能够增加一个delta差量模型文件/_delta/a/main.wf.xml,它表示针对客户a定制的main.wf.xml,Loader会主动辨认这个文件的存在,并主动运用这个文件,而全部现已存在的事务代码都不需求被修正。

假如咱们只是想对原有的模型进行微调,而不是要彻底取代原有模型,则能够运用x:extends承继机制来承继原有模型。

Loader<Possible Path> = Loader<stdPath + deltaPath>
                      = Loader<deltaPath> x-extends Loader<stdPath>
                      = DeltaModel x-extends Model
                      = Possible Model

在Nop渠道中,模型加载器实践上是分化为两个过程来完结

interface IResource{
    String getStdPath(); // 文件的规范途径
    String getPath(); // 实践文件途径
}
interface IVirtualFileSystem{
    IResource getResource(Strig stdPath);
}
interface IResourceParser{
    IComponentModel parseFromResource(IResource resource);
}

IVirtualFileSystem供给了一个相似Docker容器所运用的overlayfs的差量文件体系,而IResourceParser担任对一个详细的模型文件进行解析。

可逆核算理论提出了一个通用的软件结构公式

App = Delta x-extends Generator<DSL>

基于这一理论,咱们能够把Loader看作是Generator的一个特例,把Path看作是一种极小化的DSL。当依据path加载得到一个模型方针之后,咱们能够继续运用可逆核算的公式对此模型方针进行转化和差量修订,最终得到咱们所需求的模型方针。举个比如,

在Nop渠道中咱们界说了一种ORM实体方针的界说文件orm.xml,它的效果相似于Hibernate中的hbm文件,大致格局如下:

<orm x:schema="/nop/schema/orm/orm.xdef" xmlns:x="/nop/schema/xdsl.xdef">
  <entities>
    <entity name="xxx" tableName="xxx">
       <column name="yyy" code="yyy" stdSqlType="VARCHAR" .../>
       ...
    </entity>
  </entities>
</orm>

现在需求为这个模型文件供给一个可视化规划器,咱们需求做什么?在Nop渠道中,咱们只需求增加如下一句描绘:

<orm>
   <x:gen-extends>
      <orm-gen:GenFromExcel path="my.xlsx" xpl:lib="/nop/orm/xlib/orm-gen.xlib" />
   </x:gen-extends>
    ...
</orm>

x:gen-extends是XLang言语内置的元编程机制,它是在编译期履行的代码生成器,能够动态生成模型的基类。<orm-gen:GenFromExcel>是一个自界说标签函数,它的效果是读取并解析Excel模型,然后依照orm.xml格局的要求来生成orm界说文件。Excel文件的格局如下图所示:

excel-orm

Excel模型文件的格局其实非常接近于日常中咱们运用的需求文档格局(示例中的Excel文件格局自身便是从需求文档中复制粘贴得来的)。只需求编辑Excel文件即可完结对ORM实体模型的可视化规划,而且这种规划修正是即时生效的!(借助于IResourceComponentManager的依靠追寻才能,只需Excel模型文件产生修正,orm模型就会被从头编译)。

有些人或许对Excel的编辑方法不满意,希望选用相似PowerDesigner这种图形化的规划器。No Problem!只需求互换一下元编程生成器即可,真的便是一句话的工作。

<orm>
   <x:gen-extends>
      <orm-gen:GenFromPdm path="my.pdm" xpl:lib="/nop/orm/xlib/orm-gen.xlib" />
   </x:gen-extends>
    ...
</orm>

现在咱们就能够愉快的在PowerDesigner中规划实体模型了。

上面这个比如集中体现了可逆核算理论中所谓表象转化(Representation Transformation)的概念。真实重要的是中心的ORM模型方针,可视化规划只是在运用这个模型方针的某种表象,不同表象之间能够进行可逆转化。表象并不是仅有的! 而且咱们需求注意到,表象转化彻底不需求涉及到运行时(即规划器不需求知道ORM引擎的任何相关信息),它彻底是方法层面的工作(相似于数学层面的某种方法改换)。现在很多低代码渠道的规划器无法脱离特定的运行时支撑而存在,这实践上是一个不必要的限制。

现在还有一个有趣的问题。为了支撑<orm-gen:GenFromExcel>,咱们是否需求编写一个特定的Excel模型文件的解析器,用于解析具有示例格局的Excel文档?在Nop渠道中,这个答复是:不需求

orm模型本质上是一个Tree结构的方针,这个Tree结构需求满意的束缚条件在orm.xdef文件中现已进行了界说。Excel模型是orm模型的一个可视化表象,它也必定能够映射为一个Tree结构。假如这种映射是经过一些确定性的规矩能够描绘的,则咱们就能够运用一个一致的Excel解析器来完结模型解析。

interface ExcelModelParser{
    XNode parseExcelModel(ExcelWorkbook wk, XDefinition xdefModel);
}

所以,实践状况是,只需界说了xdef元模型文件,咱们就能够运用Excel对模型文件进行规划。而在界说了xdef元模型的状况下,模型的解析、分化、兼并、差量定制、IDE提示、断点调试器等都是主动得到的,无需额外进行编程。

在Nop渠道中,根本的技能战略便是xdef是国际的源起,只需有了xdef元模型,你就主动拥有了前后端的全部。假如你不满意,差量定制会协助你进行微谐和改进。

在示例的Excel模型文件中,格局是相对自由的。你能够随意的增删队伍,只需它能够以某种天然的方法转化为Tree结构即可。假如选用高比格的范畴论的术语,咱们能够说ExcelModelParser并不是一个从单个Excel模型方针转化到单个Tree模型方针的转化函数,而是一个效果于整个Excel范畴,将其映射为Tree范畴的一个函子(函子效果于范畴中的每一个方针上,并把它们映射为方针范畴中的一个方针)。范畴论解决问题的方法便是这么夸大,它经过解决范畴中的每一个问题,然后声称一个详细的问题被解决了。这么张狂的计划假如能够成功,那么仅有的原因便是:It’s science。

最终从头强调一下可逆核算的关键点:

  1. 全量是差量的一种特例,因而原先的装备文件自身便是合法的差量描绘,可逆核算改造能够彻底不需求修正现已存在的装备文件。以百度的amis结构为例,在Nop渠道中为amis的json文件增加可逆核算支撑,只是把装载接口从JsonPageLoader变成IResourceComponentManager,原则上不需求改动原有的装备文件,也不需求变化任何运用层面的逻辑。

  2. 在进入强类型国际之前,存在一致的弱类型的结构层。可逆核算能够适用于恣意Tree结构(包含且不限于json、yaml、xml、vue)等。可逆核算本质上是一个方法改换问题,
    它能够彻底不涉及到任何运行时结构,能够成为多阶段编译的上游部分。可逆核算为范畴特定言语、范畴特定模型的结构、编译、转化等供给了一系列的基础架构支撑。
    只需运用可逆核算内置的兼并操作和动态生成操作,即能够通用的方法完结范畴模型的分化、兼并、笼统。这种机制既能够用于后端的Workflow和BizRule, 也能够运用于前端页面。相同的,它能够运用于AI模型,分布式核算模型等。仅有的要求便是,这些模型需求以某种结构化的Tree方法来表达。比如,将这一技能运用于k8s,本质上与k8s现在力推的kustomize彻底一致。zhuanlan.zhihu.com/p/64153956

  3. 任何依据名称加载数据、方针、结构的接口,例如loader、resolver、require等函数,都能够成为可逆核算的切入点。 表面上看起来途径名现已是最简略的、无内涵结构的原子概念,但可逆核算指出任何量都是差量核算的成果,都存在内涵的演化动力。咱们能够不把途径名被看作是指向一个静态方针的符号,而把它看作是指向一个核算成果的符号,一个指向或许的未来国际的符号。 Path -> Possible Path -> Possible Model

小结

简略总结一下本文中所介绍的内容

  1. 线性体系好

  2. 多重线性体系能够化归为线性体系

  3. 线性体系的中心是 Loader:: Path -> Model

  4. Loader能够扩展为 Possible Path -> Possible Model,加载 = 合成

  5. 可逆核算理论供给了愈加深刻的理论解释

基于可逆核算理论规划的低代码渠道NopPlatform已开源:

  • gitee: canonical-entropy/nop-entropy
  • github: entropy-cloud/nop-entropy
  • 开发示例:docs/tutorial/tutorial.md
  • 可逆核算原理和Nop渠道介绍及答疑_哔哩哔哩_bilibili