1.3 元数据模型定义
本书的重点是介绍如何采用灵活的元数据模型设计低代码开发平台,允许模型可灵活扩展,以适应不同场景下的个性化需求,使得应用具有很强的适应性和重用性,尽量避免个性化开发,且通过可视化配置来定制应用。
为了解决模型的灵活性,不能在模型PartyBO枚举每一个属性,而采用表达能力更加泛化的类型来定义模型。比较简单的方式是采用Map〈String,Object〉数据结构,例如,PartyBO 定义如下:
这种定义方式确实具有很强的灵活性,对于不同业务场景下的不同模型,可以在属性values中存放不同属性值,而对应的服务接口保持不变。服务接口实现代码可以动态访问Map对象中每个属性,并进行逻辑处理。但是使用者无法判断PartyBO到底包含了哪些属性,只有当程序运行时才能从values遍历访问中确认具体存放了什么属性。而且在创建PartyBO对象时,没有信息可以让我们需要初始化PartyBO对象的哪些属性。这种模型将业务领域知识保留在系统外部,导致系统很难维护。
为了解决这里的问题,我们引入元数据模型,对模型的数据结构自身进行描述。元数据模型包含结构定义和实例两部分内容。结构定义部分用于描述数据结构,即模型,实例部分用于描述具体领域模型的对象。上述基于Map设计出来的PartyBO属于实例部分,缺少结构定义部分,即缺少描述数据结构的信息。描述数据结构首先要描述每一个属性,确定每一个属性的代码、名称、文字描述和数据类型。如下class Vd描述了属性,每一个Vd对象就是描述一个属性结构的信息。
在Vd的基础上,定义一个Dna类,将多个Vd对象组织在一个逻辑单元中,它属于元数据模型的定义部分。Dna类的代码如下:
这是一个递归树结构,由自身的基本信息和其下孩子组成,用于描述层次,表达类似于PartyBO与PartyAccountBO之间的关系。每一个Dna对象有一个List〈Vd〉 vds的属性,用于描述Dna包含的属性(Vd类型)列表。businessType用于对Dna对象进行业务分类,businessType和dnaCode可以唯一地确定一个Dna对象,相当于组合主键。dnaName代表结构名称,dnaDescription用于描述结构。minCount和maxCount用于描述该Dna对象作为孩子时,其对应的实例对象个数是多个、单个还是零个。如果maxCount大于1,表示有多个实例对象,否则,就是一个单实例对象。
例如,一个客户下面有零到多个账户,那么账户数据结构描述的Dna对象中,应将maxCount设置为大于1,minCount设置为0。cursive表示该结构实例是否属于递归结构,例如,当Dna对象描述一个菜单结构时,设置cursive为true,表示该Dna对象的实例是一个递归结构,符合菜单是一个递归结构的特点。当Dna对象描述一个组织机构时,设置cursive为true,表示一个机构下面还有其他子机构。
Dna上的属性category和secondCategory表示Dna所属的一级分类和二级分类,是为了管理上的方便引入,本书不做详细介绍。Dna的属性dbMapCode是与数据库映射相关的,在后面章节介绍。
Dna有一个构造函数,用于同时给businessType、dnaCode、dnaName和dnaDescription赋初值,利用该构造函数可以减少代码行数。
每一个Dna的对象描述一个领域的模型或者子模型结构,例如,PartyBO对应的Dna对象如下:
在上述代码中,CodeDefConst是一个Java类,其中包含系统的常量定义(具体取值不影响对程序含义的理解,为节省篇幅,书中不列举每一个常量的具体值)。在上述例子中,Dna对象partyDna描述了PartyBO类的数据结构,包含许多属性,如当事人代码(partyCode)、当事人名称(partyName)、性别(gender)、证件类型(certType)、证件号(certId)等,每一个属性是对象partyDna中vds列表属性中的一个Vd对象。
Dna对象partyDna除拥有多个Vd对象,用于描述PartyBO基本信息外,还要描述PartyBO类下的账户信息。上述代码调用getPartyAccountDna创建用于描述账户信息的Dna对象,并将其作为孩子加入partyDna孩子children中,描述了PartyBO里嵌套的List〈PartyAccount〉 accounts的结构(PartyAccount)。getPartyAccountDna的代码如下:
该方法创建描述当事人账户结构的Dna对象partyAccountDna,表示一个账户结构由账号(accountNo)、账户名称(accountName)、银行名称(bankName)、开户地址(address)和邮编(postCode)组成。该Dna对象的maxCount大于1,表示一个当事人可以有多个账户。Dna对象partyAccountDna下有一个孩子Dna对象,用于描述每一个账户用途的数据结构,表示每个账户每日使用限额情况,通过getAccountUsageDna方法返回,添加到partyAccountDna孩子children列表中。getAccountUsageDna方法的代码如下:
该方法创建描述账户用途结构的Dna对象accountUsageDna,包含两个属性定义:用途说明(usageDescription)和限额(amountLimit)。该Dna对象的maxCount大于1,表示一个账户有多个用途。
经过上述代码分析,getPartyDna方法创建了一个描述当事人结构的Dna对象partyDna,该对象共包含三个Dna对象节点,分别用于描述当事人、账户和账户用途的结构信息。显然,通过程序代码创建Dna对象partyDna,描述 PartyBO的数据结构,远比直接编写Java class PartyBO类要复杂很多,但是它的优势是可以动态改变数据结构,不仅可以描述个人客户的数据结构,还能描述企业客户、菜单和其他领域模型的数据结构,具有很强的通用性。实际上,Dna对象用于描述数据结构的信息,通常不是通过写代码创建Dna对象,而是通过可视化界面配置Dna对象。
低代码开发平台通过配置不同Dna对象来实现对不同领域模型的结构描述,意味着支持领域模型的差异性而不用修改程序代码,能够快速响应业务需求。这里通过程序代码创建Dna示例来说明元数据模型背后的机制,后续章节将会介绍如何配置Dna对象。