元数据驱动的思想
元数据驱动的思想应该不会陌生,但元数据驱动的实践应该会非常陌生。
因为元数据驱动架构是为了解决高频个性化的复杂业务而诞生的,而这种业务场景只存在2B领域。
有关元数据驱动的架构思想,在这里暂先简单抛几个点。(详细的会后续单独文章讲述)
1. 什么是元数据驱动?怎么识别?
原先前端页面都是一个个page代码写出来的。
- 元数据驱动:页面是根据配置数据生成的。这个有关页面的定义的配置数据就是页面元数据。
原先DB库表是根据一个个业务对象具象化出来,专表专用的。
- 元数据驱动:关于具体业务数据表的定义是一份配置而已,由这份配置来承担业务对象数据表的定义、解释、转换。那么,元数据驱动的架构下能定义无数个具体的业务数据表。就达到了通用化、可定制的效果。
至此,是否有此感悟?这本质上就是在原先代码写死之上加了一层,由这层抽象“解释、代劳”原先代码写死要承担和输出的能力和职责。而支撑这个抽象层的数据模型就是元数据架构。
业界非常多资料解释“元数据”是“数据的数据”。
这个解释其实仍旧非常的晦涩。
我这么解释,看是否有助于您的理解:
元数据是一套介于功能/呈现和数据之间的抽象模型,用于转换/翻译数据的一套配置数据。
2. 表单元数据案例
一个具体表单详情的元数据构成
1、表单配置数据(渲染配置、交互配置、各类规则配置、各类自定义代码实现等)
2、用户填写的真实数据
3、工作流数据
4、流转日志数据
5、消息待办
6、表单“打印”和“导出”的能力
3. 表单配置数据:
3.1 UI类
展示、布局配置
自定义提示:hint、hover、帮助说明
3.2 交互控制类
可见性、可编辑性
自定义onChange响应
条件性控制可见
3.3 校验类
组件自带常规检验
编程式代码片段执行校验
3.4 取值类
静态值域预设置
动态ajax请求 + 数据映射
其他组件特有配置
例如 DatePick组件,是否展示“今天”按钮的配置项
4. 为啥要用元数据驱动
企业信息建模的核心是面向对象,面向对象(Object-Oriented)将客观世界看作由对象组成的,对象由属性和操作组成,对象可按其属性进行分类,对象之间的联系通过传递消息来实现。
元数据是支撑企业信息数字化建模的地基,元数据(metadata)是描述对象的数据,对对象的属性、操作及联系的描述性信息。
元数据驱动是一种设计模式,也是一种开发模式,是用简单架构去构建复杂业务的灵魂。元数据驱动的分层建模体系是建立在元数据基础上对实体进行业务分层,上层业务是构建在底层业务实体基础上的,这样无论咋样复杂的业务都可以解决。
5. Saas为啥要用元数据
如果是工具类Saas服务,或者扩展能力稍微弱一点的Saas服务,都没必要上元数据,DB都有自己的元数据管理体系,使用起来很容易。
但如果是扩展性要求较高,或者需要构建PaaS平台的Saas服务,就需要自己来管理元数据,这样能集成较多自己的功能。
6. Saas为什么要用元数据驱动开发
管理元数据: 是一种解决可变性字段/扩展字段的一种解决方案
元数据驱动开发:是一种开发模式
把元数据自己管理,完全从可以技术上解决。但为什么开发要按照元数据驱动呢?
6.1 项目性(单个租户)的系统
想想自己日常的开发,尤其是ORM,CRM,ERP等传统软件开发页面,大部分是不是**列表,详情,添加/编辑,导入导出,跳转到关联的多方列表, 审批流程,跟外界交互(消息,Email等),操作A影响B 等类似操作,**如果只是项目性(单个租户)的系统,直接去DB建表实现即可,
6.2 多租户
但如果要兼容多租户特性化需求就需要,从问题的本质更抽象上一层,抽象软件里边的操作。
如: 创建的每张表,可以为一个实体,实体有很多字段,有一些规则和约束来保证数据完整性,对这个实体需要CRUD操作,操作实体可能触发一些操作,或者要修改一个状态需要一个审批流程。
实体和实体之间有联系,可以通过订单查看购买商品的列表,也可以在订单页面添加商品和编辑商品。
订单到了一定状态,可能需要生成一条开发票的信息等等。这样的逻辑是不是在开发过程中经常遇到,但Saas就是基于这一层来开发,所以使用元数据驱动,相当于基于实体,实体关系,实体相互影响这样的面向对象的方式来组织业务,这就是基于元数据驱动的开发模式。
相关联的架构模式,如果基于DDD领域驱动的架构模式,其实是从架构,需求沟通,代码组织等方面来落地基于面向对象的开发模式方法论。
从每个租户视角来看,每个租户都在一个共享数据库内拥有一个基于租户标识 OrgID 来隔离的虚拟的租户数据库。
元数据实体包括 Objects 和 Fileds 实体以及实际数据 Data 实体都包含租户 OrgID,这样就可以通过租户 OrgID
来天然隔离各租户的数据,当然不止这些实体,包括索引相关等透视表实体也使如此。
操作抽象
7. 元数据驱动开发过程
7.1 实体:(静态)
-
字段属性:名称,key,可见性,是否常用,类型,是否必填,系统字段(只可以代码中获取到,不可编辑)
-
完整性:字段长度,数据范围,校验规则
-
唯一性:主键ID,业务联合主键(查重规则),唯一性字段,自动编号
-
索引:根据业务字段创建索引
-
关联关系:设置关联的实体,字段(默认按照ID管理,展示的是字段)
7.2 实体:(动态)
-
新增,详情,编辑…… 等操作,预留前端或者后端代码位置,可以控制展示特征(如:顺序,可见性,顺序,可编辑)
-
打印模板定义(如需要)
-
操作前后的自定义逻辑,预留给实施人员,集成人员,二次开发人员
-
单项流程(名字我自己起的):由实体的某个属性触发,单向异步 (触发后就不管了)
-
中断流程(名字我自己起的,其实就是审批流): 某个实体的某个属性变更需要 流程进行后才可以生效,否则就不能变更
-
状态/阶段:这两个是对实体一些具有生命周期的字段的单独体现,用的比较多,比如订单的状态变更
-
流程,状态便跟前后增加自定义逻辑
7.3 实体互动
操作 - 事件 - 操作
- 变动监控:
A: 变动属性值的时候,同步调用外部业务逻辑并触发流程
B:变动属性输出日志;异步监控日志,生成事件,进而触发事件中的操作
- 影响:
A:生成M条B或者C数据
B:修改B的属性值
C:跟外界交互
元数据驱动结构
8. 注意点
-
架构上需要考虑:paas平台,开发态,实施集成。区别:PaaS平台是专门平台的开发组,切记建立同开发,实施的沟通机制,防止平台组自说自话。 开发小组:可以写代码,架构不好的开发和平台代码在一块,导致开发学习成本,沟通成本极高; 架构的时候预留的接口需要考虑开发的实际开发的流程,代码管理,可实现的功能等方面内容,否则业务开发小组在驱动不了架构平台组的时候只能自己找代码修改。
-
考虑大批量操作对底层的影响,以及及时业务操作的实时性业务需求。大批量数据操作bulk接口,必须为异步,包括内部对:实体操作前后,可能触发的流程或者事件,如果是同步的话, 必然会对系统有较大的压力,从而影响正常的业务流程。可全部为异步操作,只进行数据的完整性校验,然后记录日志,保证这部分业务的事务原子特性,其他的操作根据操作日志来异步分析和同步
-
统一接口很重要:API标准,文档必须完善; 条件允许需要暴露查询语言。 一定要屏蔽元数据服务底层逻辑,否则上层应用开发将异常艰难,这些标准需要适当的培训和沉淀。 并且平台需要提供一定的工具,类似Mysql的Navicat一样可以方便查询,用来查询问题
-
预留给开发态需要考虑小组分工,尽可能从代码层面做到解耦。而且不同业务小组的代码也可能解耦,否则就是灾难性的。
9.附录 元数据驱动的多租户架构
Salesforce 将 Force.com 定义为 PaaS 平台,Force.com 的基础就是元数据驱动的软件架构来支撑多租户应用。首先我来解释下什么是以元数据驱动的软件架构为核心。
一、多租户意味着什么
多租户的含义用一句话来描述就是:一个云平台,无数多个客户。
一个云平台的含义是:一个代码库,一个数据库,一整套共享的可扩展服务,包括数据服务、应用服务以及 Web 服务。
无数多个客户的含义是:每个客户都被分配一个唯一的租户 OrgID,所有的数据存储都是按照租户 OrgID 隔离的,所有的数据访问必须包含 OrgID,所有的操作也都是包含租户 OrgID 的,也就是所有的客户数据和行为都是被安全的通过唯一的租户 Org 进行严格隔离的。
每个租户/组织只能看到和定义按照自己租户 OrgID 隔离的自己版本的元数据和数据,而且只能执行自己租户 OrgID 所授权的行为,这样每个租户就拥有各自版本的 SaaS 方案。
二、元数据驱动意味着什么
元数据对于平台意味着平台数据的数据,对于租户意味着是关于租户数据的数据。
当用户定义一个新的用户表的时候,用户创建的不是数据库中的物理表,而是在系统态的元数据表中添加了一条记录,这个记录描述的是用户表的逻辑定义,是虚拟的,这个表并不在数据库中物理存在,而这条记录代表就是用户态的数据表。
当用户定义了用户表的一个新的字段时,用户并没有在物理表中创建物理字段,而是在系统态的元数据表中添加了一个记录,这个记录描述的用户表的字段组成的逻辑结构,是虚拟的,这个字段也不在数据库表结构中物理存在,而这条记录代表的就是用户态的用户表字段。
也就是通过存储在系统态的元数据表中的元数据记录作为虚拟用户的数据库结构。
三、元数据驱动的多租户整体架构
我们先来大概了解下元数据驱动的多租户的整体架构,整体架构大概分为 5 个逻辑层次:
底层数据架构分为三个层次:
-
最底层是数据层,存储了离散的系统和用户的业务数据,业务日常运营的数据存储在这里。
-
公共元数据层,存储了应用系统标准的对象和标准的字段定义,对底层数据的结构进行定义说明。
-
租户特定元数据,存储了租户自动的对象和自定义的字段定义,用于对底层的数据结构进行定义说明。
-
通用数据字典 UDD(Universal Data Dictionary) 运行引擎层实现了应用对象到底层数据存储的映射,包含对象模型操作、SOQL 语言解析、查询优化,全文搜索等功能,我们常说的 ORM 功能也是其核心功能,但比其复杂的多。
-
平台服务层提供 PaaS 层平台服务,提供应用对象模型的创建,权限模型创建,逻辑和工作流程创建以及用户界面的创建,包括屏幕布局、数据项、报表等
-
标准应用层提供端到端的标准的业务应用功能。
-
租户虚拟应用层,用户可以在标准应用层或者平台服务层之上定义自己特有的业务应用功能,满足自己特定的业务场景需要。
其中,底层数据架构是最为关键的平台基石(The Corner Stone),其核心运行引擎也是基于强大的底层数据架构基础上构建的。本文则以元数据驱动的多租户数据架构为核心来一一展开。
四、元数据驱动的多租户数据架构
下面我们具体来看下系统态的数据模型,基于 Salesforce 加上个人推理的元数据驱动的多租户数据模型。注意:由于 Salesforce 并未有对核心逻辑进行完全公开和说明,所以本文所整理的部分核心模型包含了个人的逻辑推理和解读,但是确实进行了逻辑验证和场景验证,如有纰漏和不全面的地方,欢迎讨论及指正。
Salesforce 云服务平台遵循的是面向对象的设计理念,所有的实体、实体关系以及实体的 CRUD 均是以对象的视角进行的,所以其元数据驱动的多租户数据模型的存储基本元素也是按照对象的颗粒度进行存储,源自于 OO 的对象间引用,同普通关系数据库主外键关系异曲同工,只是细节处理上不尽相同,请大家注意这一点。
1. 元数据驱动的多租户数据架构概览
首先,我们先来大概了解下元数据驱动的多租户模型的核心内容,元数据驱动的多租户的数据模型主要分为三个部分:元数据表、数据表和功能透视表。
元数据表(Metadata Tables)
元数据表用于存放系统标准对象以及用户自定义对象和字段定义的元数据,也就是系统和用户对象的逻辑结构,即对应于关系数据库中的虚拟表结构。元数据表主要包括Objects 表以及 Fields 表,是系统标准对象和用户对象定义数据的仓库,即元数据仓库。
数据表(Data Tables)
数据表用户存放系统以及用户对象和字段的实际数据,实际的用户业务数据以及应用系统相关数据存放在这里。数据表包括 Data 表和存放大文本数据的 Clob 表,数据表存储了绝大部分用户的实际数据,是一个巨大的用户业务数据仓库。
功能透视表(Specialized Pivot Tables)
功能透视表包含了非常关键的关系表、索引表、关系表以及其他特定用途表。例如关系表定义了对象间的关系,索引表解决虚拟结构索引的问题,这部分后续将进行详尽的介绍。
示例模型数据
数据库物理表数据:Customer
数据库物理表数据:Product
数据库物理表数据:Order
数据库物理表数据:OrderItem
- 实体表关系
Order 表同 OrderItem 为父子表,通过 OrderID 进行主外键关联;Customer 表同 Order 表为父子表,通过 CustomerID 进行主外键关联;Product 表同 OrderItem 表为父子表,通过 ProductID 进行主外键关联。
- 用户自定制
用户有执行 DDL 权限,可以在自己租户数据库内在进行扩展模型自定义,建立自定义的物理表,索引,关系等。
- 问题和风险
用户具有执行 DDL 权限,可以自定义数据库物理模型,会带来各租户的自定义数据模型大爆炸,会给后续平台模型定义升级冲突,造成模型升级的巨大的障碍
同时,由于系统标准模型和用户模型均为物理模型,未有做系统标准和自定义数据的有效隔离,如何保证平台应用的每一次升级必然会考虑对现有用户自定义模型的稳定性和可用性的影响,在自定义物理模型的情况下,不仅挑战巨大,而且包含巨大的回归验证的工作量,很难收敛。
当用户执行 DDL 时,通常会锁定数据库物理资源,当数据库数量非常巨大时可能会带来不可控的 downtime,对应用系统的可用性造成巨大的影响。如果数据库是每个租户各自独占,还只会影响到单个租户;如果是多租户共享数据库,则可能会影响到其他租户,影响是灾难性的。作为云平台服务商,不管是用户操作还是系统行为,我们都不期望我们的设计对用户系统的可用性造成影响,所以用户执行 DDL 的行为是否允许确实有待商榷,但是如果不允许,用户可扩展性在这种设计环境中必然受到一定程度的限制。
(2)元数据驱动的多租户数据模型(Metadata Tables)
前面章节描述了元数据驱动的多租户模型简单模型图,本小节详细解说下每个核心实体表的核心结构,同时已知资料部分较为简略,无法描述模型全貌和核心细节,为了模型完整性,整体数据模型包含了作者思路推理部分,用以来完整清晰地定义模型。当然由于所有模型都是主观的(subjective),仅代表个人观点,欢迎大家的不同的观点,一起讨论改进。
正如前面介绍“一个云平台”时提到,通过一个统一的数据库来支撑无数个租户,所以元数据驱动的多租户模型是基于一个共享数据库的前提。当然多租户实现设计多种多样,大家可以不拘泥此种。
1)元数据表之对象定义表:Objects 表
Object 系统表存储了每个租户为它的扩展应用对象定义的元数据,包含如下核心字段:
- ObjID:应用对象唯一标识,具有固定长度和格式。
- OrgID:应用对象所归属的租户 ID,用于统一共享数据库内的多租户数据隔离,通常和租户定义的域名对应。
- ObjName/Name:对象名称,用于系统配置和开发(developer name)。
- Label: 对象的显示名称。
除了用户自定义对象,系统的标准对象也是采用相同的方式进行定义的。
2)元数据表之字段与关系定义表:Fields 表
Fields 系统表存储了每个租户为他的扩展应用对象字段定义的元数据,包含了其所归属的应用对象的租户 OrgID,字段所属对象的 ObjID,字段定义标识 FieldID,字段名称FieldName,字段存储位置定义 FieldNum,数据类型 DataType。数据类型重要补充关联字段(DigitLeft,Scale,TextLength,RelatedTo,ChildRelationshipName)以及是否必选、唯一、索引标记,还有部分标准字段。Fields表非常关键,其不仅定义了普通的应用对象字段,包括基本信息和数据类型信息,而且通过特殊关系字段对不同应用对象之间的关系进行定义,详细说明如下:
- FieldID:此对象字段的唯一标识,具有固定长度和格式
- OrgID:其所归属的应用对象所归属的租户 OrgID
- ObjID:字段所属对象的 ObjID
- FieldName/Name:字段名,用于系统配置和开发(developer name)。
- Label:字段展示名称,用以展示给最终用户。
- FieldNum:对应到 Data 数据表的数据存储字段映射,暨 Data 表中 ValueX 字段中的X。
- DataType:指定此对象字段的数据类型包含普通类型:Number、TEXT、Auto
Number、Date/Time、Email、Text Area等,也包含特殊的关系类型如:Look
up关系类型、Master-Detail 关系类型等。 - DigitLeft 和 Scale:用于 Number、Currency、Geolocation 等数字数据类型的关联设定,例如定义了一个字段的 DateType 为 Number,则需要指定其整数部分的最大位数 DigitLeft 和小数部分的最大位数 Scale,两部分长度总和不超过 18 位。
- TextLength:当数据类型为 TEXT 时启用,用于指定 TEXT 类型的字符的长度限制。
- RelatedTo 和 ChildRelationshipName:这两个字段当 DateType 为关系类型(Lookup,Master-Detail 等)时会启用,其中 RelatedTo 保存关联的应用对象 ID,ChildRelationshipName 用于保存父子关系中子方的关系名称,同一个父对象的子方的关系名称唯一,用于关系的反向查询。
- IsRequired:此字段数据保存时,是否校验值的存在。
- IsUnique:是否允许重复值。
- IsIndexed:此字段是否需要建索引。
其他字段:此处仅列举了说明模型所需要的字段,其他字段暂不进行列举,不列举原因和其重要性并无直接关联。
3)数据表(Data Tables)之关系数据表:Data 表
MTData 系统表存储了 MTObjects 和 MT_Fields 元数据表内定义的数据对象(表)所对应的数据,一一映射到不同的租户各自定义的表和表中的字段(对象和对象字段)。
- GUID:数据表的主键,用于存放每个应用对象实例的标识 ID。
- ObjID:其所归属的应用对象所归属的租户 OrgID。
- Name:应用对象实例名称。
- Value0…Value500:用于存放对象实例字段的数据,其 ValueX 中 X 值对应到 Fields 表中 FieldNum
定义,ValueX 存放的数据,不管原始数据类型、存储格式均为变长字符串格式。
4)数据表(Data Tables)之非结构化数据表:CLobs
MT_Clobs 用于存储大字符段的存储 CLOB,同时 CLOB 也存储在数据库外的索引结构中,用于快速的 Full-Text 文本检索。
- 元数据模型核心实体关系图
我们在应用系统开发中,通常我们定义的数据结构包括数据表、表字段,索引通常都会直接定义在物理数据库中,创建物理的表和字段以及索引等。
但是在元数据驱动平台数据模型中,我们定义的用户表包括系统表都是逻辑表,其结构是虚拟的,用户表的定义存储在 Objects 表,对应的字段定义存储在 Fields 表中,实际用户数据存储在 Data 表中。特别注意的是,对象的引用关系定义也定义在 Fields 表中,以特殊数据类型方式来定义。(另:Relationships 表后面章节进行描述)。
从每个租户视角来看,每个租户都在一个共享数据库内拥有一个基于租户标识 OrgID 来隔离的虚拟的租户数据库。
元数据实体包括 Objects 和 Fileds 实体以及实际数据 Data 实体都包含租户 OrgID,这样就可以通过租户 OrgID 来天然隔离各租户的数据,当然不止这些实体,包括索引相关等透视表实体也使如此。
4、标准对象与标准字段
前面整体架构层次里提到了公共元数据层和标准应用层,公共元数据层提供了标准对象和标准字段的定义。
其中标准对象为每个租户提供公共端到端的应用的标准应用功能。
同时用户可以在标准的对象基础上扩展自定义的应用对象,满足自己的特定业务场景。__c 后缀代表自定义,后续详解。
而标准字段则提供给每个对象包括自定义对象的共同的字段,包含部分业务字段和非业务字段。
用户也可以在标准对象和自定义对象内自定义不同的字段,以满足业务需要。__c后缀代表自定义,后续详解。
5、对象关系类型
应用对象关系类型主要分为 Look up 和 Master-Detail 两种关系类型,其中 Look up 为弱的父子关系类型,Master-Detail 为强的父子关系类型,其特性对比如下。
8、多租户索引透视表 (Pivot Tables)
1)Indexes 透视表
大多数结构化的数据存储在 Data 表内,如前面提到的,所有这些不同类型数据都是以可变字符串的形式存在 ValueX 列里面如各种数字以及日期等全部都是以可变字符存储的,这样虽然对于对象实例各种字段的存储确实非常灵活,不同的列可以存储不同类型的数据,即使同一 ValueX 列不同的对象也可以存储类型的数据,但是这样带来一个巨大的问题,由于不同的数据类型以可变字符串的方式存储在同一列内,你没办法利用底层数据库索引的能力对其进行排序,ValueX 列的数据都是一种按照离散的顺序来存储的。传统的数据库依赖原生的数据库索引来快速在数据表内定位到符合查询条件的记录。而按照 Data 表ValueX列的数据存储情况,在 Data 表建立 ValueX 列的索引来支撑数据快速查询是不现实的。
所以解决办法就是建立另外的透视表叫做 Indexes 索引表,并把数据拷贝出数据表并转换成原始的的数据类型,并存储到Indexes索引表列内,如原来是整形的数据以可变字符串的格式存储 在ValueX 列中,拷贝到 Indexes 表之前通过函数将其转换为原始的数据类型,在存储到 Indexes 对应的 NumValue 列内,以方便建立索引,Indexes 表包含强类型的索引类,像 StringValue,NumValue,DataValue,用来定位对应数据类型的字段数据。
Indexes透视表的字段说明如下:
OrgID:其所归属的应用对象所归属的租户OrgID
ObjID:字段所属应用对象唯一标识
FieldNum:对象字段存储位置
ObjInstanceGUID:对象实例唯一标识
StringValue:强类型的字符串列
NumValue:强类型的数字列
DateValue:强类型的日期列
————————————————