資安

元数据驱动的SAAS架构

 

元数据驱动的SAAS架构

道冲而用之或不盈,渊兮似万物之宗

—老子

专用聪明,则功不成,专用晦昧,则事必悖,一明一晦,众之所载

—鬼谷子

君子屈于不己知而伸于知己

—吕氏春秋

年初的计划立了三个方向FLAG,已经码了一个方向:聊聊面向服务的架构,现在开工第二个方向,希望今年完成所有三个方向。

 

扯淡

前一段时间拖拖拽拽看了电视剧”长安十二时辰“,一路上都是张小敬的一夫当关,万夫之勇,还有各美女相互陪衬,出人意料的是徐宾,一个八品小吏,人微言轻,但总想做出点让自己得意的事,才好闭眼,徐宾选择的是铤而走险,穷其个人谋略,虽然最后挂掉了,但是目的达到了,随了自己的心意。其实这个剧有个人并没有得到应有的聚焦,这个人就是中唐名相—李泌,李泌所处时代说好听点是中唐,说不好听点是乱唐。世人只知盛唐魏征、房玄龄,鲜有人闻乱唐名相李泌。李泌,中唐史上传奇名相,几乎和郭子仪相始终,身经四朝-玄宗、肃宗、代宗和德宗四代帝王,参与宫室大计,辅佐朝廷,运筹帷幄,对外战略,配合郭子仪等各个将领的步调,使其延续唐朝大业,善用谋略拨乱反正、安邦定国。但史评”泌有谋略,而好谈神仙怪诞,故为世所轻“实在是不公平。

 

引子

作为业务系统技术开发同学,面向当下:1.首先应该是快速搭建业务通路,让线上业务跑起来,快速试错,解决生存问题;2.第二步是在链路通了,业务基本跑起来的基础上如何支撑业务跑更快,解决快速增长问题;3.第三步:在完成支撑业务快速增长的基础上,要进行精细化提升,通过在吃撑业务快跑间隙挤时间打磨系统功能和体验,踏踏实实花时间,抽象能力,沉淀产品,提升效能;

同时我们也必须面向未来:如何在抽象能力以及沉淀了产品的基础上,如何把所承载和沉淀的业务能力快速输出,贡献给整个行业,抑或为整个社会商业生态提供基座支撑。那么面向未来,将平台产品进行SAAS化升级真正将能力进行有价值开放输出是我们提前要布局的核心方向。

那么将平台产品进行SAAS输出,需要解决那些问题呢?这里尝试把核心问题列举一下:

  1. 如何根据不同用户需求进行计算能力按需调度分配?(IAAS/PAAS)
  2. 如何满足用户数据安全性要求,严格隔离不同用户的数据,使用户只能看到自己的数据?(PAAS)
  3. 如何支持不同用户在标准的数据对象/数据模型上按需添加定义自定义的数据对象/扩展模型?(PAAS & SAAS)
  4. 如何按照不同用户进行按需功能搭配组合,满足不同用户从基础到专业级不同业务场景需求?(SAAS)
  5. 如何统一对平台产品进行升级而不影响用户已有数据及功能?(IAAS、PAAS、SAAS)

通过以上问题,我们可以看出产品SAAS化输出的关键是如何对不同的用户通过标准+扩展能力按需进行算力、数据、安全、功能有效定制,支持多用户共性和个性的问题,也暨多租户的问题,同时也涉及到计费和服务水平等相关问题。我们下面来聊下上述问题的解题关键和解题思路

  1. 第1个算力的问题核心是调度问题,弹性计算提供在IAAS层的统一算力调度能力,而Serverless则可以在PAAS层提供更高层次的算力调度能力。

  2. 第4个问题的核心是业务流程的抽象和业务功能的拆分,领域驱动的设计以及服务化(微服务)在平台功能抽象拆分提供了相对成熟的思路,催化了以纵向业务功能细分作为域划分的依据的服务化方案以及组织结构,主要诉求是在细分的业务功能服务基础上,能按需快速灵活的组合支撑不同的业务模式,提供业务敏捷性,支撑业务创新求变。

    当然放过来,由于纵向功能细分,业务功能域增多,整个业务链条上的咬合点越来越多,随之产生越来越多的数据来源冗余重复或者缺失,功能或者重合且各自发散,或者缺失,最终给整体业务带来较多数据和功能的不一致性风险,这样一来不仅横向端到端的业务串联成本高,而且关键路径的风险收敛成本比较高,矛盾冲突点集中在各纵向域功能和数据咬合处,具体表现为:

    数据上:1.无主数据,有数据需求无owner,2.大量重复且不一致数据,

    功能上:3.部分业务功能缺失,4.域之间存在业务功能重复且行为不一致。

    到底是纵向切分域还是横向分业务模式拉平来做,这个问题没有标准答案,更没有最佳答案,只有根据不同的业务发展阶段及时动态调整试错,是一个不断寻找相对最优解的动态过程

  3. 弹性计算和Serverless解决了算力的问题,领域驱动服务化设计解决了功能的拆分和按需搭配组合的问题,那么剩下核心问题就是数据了:如何以一套统一的数据架构即能支撑多租户的数据安全性需求以及通用的数据存储,也能支撑用户扩展的自定义数据对象定义和模型变更,同时也要保证数据定义层面的扩展和变更不会影响自身和其他租户的业务功能的可用性。我们来分析下可能的方案(暂不考虑按服务边界进行数据库拆分):

    1. 统一的数据库,标准数据模型和扩展数据模型直接映射到物理表和索引:很显然对于不同的租户自定义的数据对象和数据模型要求是无法支撑的,物理数据模型会相互干扰,相互冲突直到无以为继。即使是对于所有租户完全标准的功能和数据存储,平台自身的标准模型的升级的DDL也会对用户的可用性造成较大影响,所以显然是行不通的。

    2. 如果为每个租户创建各自的数据库呢?各自租户拥有各自的数据库,可以满足用户数据安全隔离的需求,也可以满足各租户自定义的数据需求,看上去像是一种合理的SAAS数据方案,但是仔细分析,发现有两个明显的问题:

      1. 如果用户需要修改或者扩展现有物理数据模型而进行的DDL操作,必然会影响线上业务的整体可用性,也可能会影响到标准数据模型,从而影响到线上功能使用。
      2. 如果用户可自定义对物理模型进行扩展和定制,当平台进行模型升级的时候,极容易产生物理模型的冲突,导致新旧功能异常。
      3. 由于用户在各自数据库存在各自定义的扩展和定制,则平台数据模型和功能升级,需要针对不同的租户进行分别验证,存在着极大的升级验证工作量和风险。

    以上两种方案可行性低,我们从其中发现的问题是平台业务系统的逻辑模型到物理模型的直接映射是造成问题的主要因素。既然物理模型的变更是平台不稳定的动因,那么我们是否通过解耦业务逻辑模型和物理模型的映射关系来尝试解决这个问题呢?

    既然问题已经定义清楚了,如何解决这个问题呢?通常我们解决架构问题的一个“万能”的方法是:增加一个层次,我们也来套用一次,增加一个层次(元数据层)来解耦逻辑模型到物理模型强映射的问题:

    1. 首先我们需要对业务逻辑模型进行抽象定义,定义出业务模型的数据,是业务模型数据化暨模型的元数据(the metadata of the logic model ),将模型结构存储为数据,而不是直接对应的物理存储结构。
    2. 其次根据定义出的元数据,也就是对数据对象定义数据,数据对象数据内容数据的存储结构进行统一抽象,形成元数据逻辑模型
    3. 将元数据逻辑模型映射到元数据物理模型,对应实际存储结构
    4. 通过对业务模型的变更变成了对元数据层的数据的变更,而不是物理结构的变更,来实现业务逻辑模型同物理模型的解耦。

     

     

    image-20190819084812295.png

     

    很多事情说起来好像挺简单,实际上是一个非常巨大的系统工程,将其付诸实践是挑战非常大的事情,而取得踏踏实实的成功更难,上述的解题思路是Salesforce的解题思路,而且Salesforce 不仅取得了成功,而且接近将其做到的极致,下面我们站在巨人的肩膀上来看看Salesforce如何通过元数据驱动的架构(核心是基础数据架构)来支撑多租户的SAAS业务平台的。注意:由于Salesforce并未有对核心实现逻辑进行完全公开和说明,所以本文所整理的部分核心逻辑包含了个人的逻辑推理和解读,但是确实进行了逻辑验证和场景验证,如有纰漏和不尽全的地方,欢迎讨论及指正。

     

    元数据驱动的多租户架构

    SalesForce将Force.com定义为PAAS平台,Force.com的基础就是元数据驱动的软件架构来支撑多租户应用。下面将元数据驱动的软件架构作为核心进行介绍。

     

    一、多租户意味着什么?

     

    多租户的含义用一句话来描述就是:一个云平台,无数多个客户

    一个云平台的含义是:一个代码库,一个数据库,一整套共享的可扩展服务包括数据服务、应用服务以及Web服务。

    无数多个客户的含义是:每个客户都被分配一个唯一的租户OrgID,所有的数据存储都是按照租户OrgId隔离的,所有的数据访问必须包含OrgID,所有的操作也都是包含租户OrgID的,也就是所有的客户数据和行为都是被安全的通过唯一的租户Org进行严格的隔离的。

    每个租户/组织只能看到和定义按照自己租户OrgID隔离的它自己版本的元数据和数据,而且只能执行自己租户OrgID所授权的行为,这样每个租户就拥有各自版本的SAAS方案。

     

    二、元数据驱动意味着什么

     

    元数据对于平台意味着平台数据的数据,对于租户意味着是关于租户数据的数据,

    当用户定义一个新的用户表的时候,用户创建的不是数据库中的物理表,而是在系统态的元数据表中添加了一条记录,这个记录描述的是用户表的逻辑定义,是虚拟的,这个表并不在数据库中物理存在,而这条记录代表就是用户态的数据表。

    当用户定义了用户表的一个新的字段时,用户并没有在物理表中创建物理字段,而是在系统态的元数据表中添加了一个记录,这个记录描述的用户表的字段组成的逻辑结构,是虚拟的,这个字段也不再数据库中表结构中物理存在,而这条记录代表的就是用户态的用户表字段。

    也就是通过存储在系统态的元数据表的元数据记录来作为虚拟用户的数据库结构。

     

    image-20190818211252847.png

     

    三、元数据驱动的多租户整体架构

    我们先来大概了解下元数据驱动的多租户架构的整体架构,整体架构大概分为5个大的逻辑层次:

    1. 底层数据架构分为三个层次:

      1. 最底层是数据层,存储了离散的系统和用户的业务数据,业务日常运营的数据存储在这里。
      2. 公共元数据层,存储了应用系统标准的对象和标准的字段定义,对底层数据的结构进行定义说明
      3. 租户特定元数据,存储了租户自动的对象和自定义的字段的定义,用于对底层的数据的结构进行定义说明。
    2. UDD(Universal Data Dictionary)运行引擎层实现了应用对象到底层数据存储的映射,包含对象模型操作、SOQL语言解析、查询优化,全文搜索等功能,我们常说的ORM功能也是其核心功能,但比其复杂的多。

    3. 平台服务层,提供PAAS层平台服务,提供应用对象模型的创建,权限模型创建,逻辑和工作流程创建以及用户界面的创建包括屏幕布局,数据项,报表等

    4. 标准应用层,提供端到端的标准的业务应用功能。

    5. 租户虚拟应用层,用户可以在标准应用层或者平台服务层之上定义自己特有的业务应用功能,来满足自己特定的业务场景需要。

    image-20190826202951624.png

    其中个人认为底层数据架构为最为关键的平台基石(the corner stone),其核心运行引擎也是基于强大的底层数据架构基础上构建的。本文则以元数据驱动的多租户数据架构为核心来一一展开。

    四、元数据驱动的多租户数据架构

    下面我们具体来看下系统态的数据模型,基于Salesforce加上个人推理的元数据驱动的多租户数据模型。注意:由于Salesforce并未有对核心逻辑进行完全公开和说明,所以本文所整理的部分核心模型包含了个人的逻辑推理和解读,但是确实进行了逻辑验证和场景验证,如有纰漏和不尽全的地方,欢迎讨论及指正。

    Salesforce云服务平台遵循的是面向对象的设计理念,所有的实体、实体关系以及实体的CRUD均是以对象的视角来进行的,所以其元数据驱动的多租户数据模型的存储基本元素也是按照对象的颗粒度进行存储,源自与OO的对象间引用,同普通关系数据库主外键关系异曲同工,只是细节处理上不尽相同,请大家注意这一点。

     

    1. 元数据驱动的多租户数据架构概览

      首先,我们先来大概了解下元数据驱动的多租户模型的核心内容,元数据驱动的多租户的数据模型主要分为三个部分:元数据表、数据表和功能透视表。

      1. 元数据表(Metadata Tables)

        元数据表用于存放系统标准对象以及用户自定义对象和字段的定义的元数据,也就是系统和用户对象的逻辑结构暨对应于关系数据库中的虚拟表结构。元数据表主要包括Objects表以及Fields表,是系统标准对象和用户对象定义数据的仓库,元数据仓库。

      2. 数据表(Data Tables)

        数据表用户存放系统以及用户对象和字段的实际数据,实际的用户业务数据以及应用系统相关数据存放在这里。数据表包括Data表和存放大文本数据的Clob表。数据表存储了绝大部分用户的实际数据,是一个巨大的用户业务数据仓库。

      3. 功能透视表(Specialized Pivot Tables)

        功能透视表包含了非常关键的关系表、索引表、关系表以及其他特定用途表。例如关系表定义了对象间的关系,索引表解决虚拟结构索引的问题,后续进行详尽的叙述。

         

        image-20190820161336844.png

       

    2. 元数据驱动的多租户数据架构详解

      上一节粗略地描述了元数据驱动的多租户模型三大部分模型实体和基本作用,大家可能会比较疑惑这么简单一个实体模型,怎么就起了这么个牛逼的名字,而且支撑了“一个云平台,无数个客户”,我们下面就对此模型的核心逻辑进行详细展开和推理说明,同时详细阐述以此模型为中心的服务来说明整个元数据层或者说UDD(Universal Data Dictionary)层的设计。

       

      土话说:“没有对比,就没有伤害”,道理是想通的,用相似的事物进行对比是对理解客观事物进行了解比较好的方法,找出其相同点,共性的地方,找出其不同点,异样的地方,同时识别出是否有不可对比的方面,从各个方面去对比,则能更全面、更深入的了解客观事物。

       

      下面我按照普通应用设计思路方式来定义一个简单直观的多租户SAAS数据架构方案示例,作为元数据驱动多租户数据架构方案的对比基准方案,用对比来更好的帮大家了解元数据驱动多租户数据模型及架构的设计逻辑。

       

      1. 普通多租户SAAS数据架构方案示例(非严谨定义,仅做示例,请勿纠结)

        1. 多租户基本思路:每个租户一个数据库,提供数据库级别的租户数据隔离,平台提供标准应用功能模型,用户可以在各自数据库内定义以及修改各自的定义模型,所有模型采用数据库物理表、索引,主外键实现。不同的租户通过路由到不同的数据库来实现隔离。

        2. 域模型样例采用大家都熟悉的最小集的订单模型实现,包含商品、用户、订单和订单详情表。注意:此简化模型仅用做示意说明,和意图无关大多数字段均省略,非严谨定义。

          image-20190826165345474.png

        3. 示例模型数据

          1. 数据库物理表数据:Customer
          Demo Table Customer            
          CustomerID CustomerNo FirstName LastName NickName LoginName CustomerStatus CreatedTime
          CI00000000000000000001 CI200903091014A0000001 Yan Cheng cy chengyan Valid 2009/8/19 13:14
          CI00000000000000000002 CI200903091014A0000002 Jun Ling lj lingjun Valid 2009/8/20 13:14
          CI00000000000000000003 CI200903091014A0000003 Tommy Valdles tom Tommy Valid 2009/8/21 13:14
          CI00000000000000000004 CI200903091014A0000004 Dorothy Franklin doro Dorothy Valid 2009/8/22 13:14
          1. 数据库物理表数据:Product
          Demo Table Product          
          ProductID ProductNo ProductName ProductPrice Currency ProductStatus CreatedTime
          PI00000000000000000001 PI201901060930A0000001 IPhone8 256G Golden 6000 CNY Online 2018/7/9 19:14
          PI00000000000000000002 PI201901060930A0000002 IPhoneX 256G Golden 10000 CNY Online 2018/7/10 19:14
          PI00000000000000000003 PI201901060930A0000003 IPhoneXR 256G Golden 8000 CNY Online 2018/7/11 19:14
          1. 数据库物理表数据:Order
          Demo Table Order      
          OrderID OrderNo CustomerID OrderStatus OrderTime
          OI00000000000000000001 ON201903091914A0000001 CI00000000000000000001 Completed 2019/3/9 19:14
          OI00000000000000000002 ON201903091914A0100003 CI00000000000000000002 Completed 2019/3/9 19:14
          OI00000000000000000003 ON201903091914A0200005 CI00000000000000000004 Canceld 2019/3/9 19:14
          1. 数据库物理表数据:OrderItem
          Demo Table OrderItem          
          OrderItemID OrderID ItemID ItemPrice ItemCurrency ItemQuantity OrderItemStatus
          OII0000000000009835101 OI00000000000000000001 PI00000000000000000001 5888 CNY 2 InProduction
          OII0000000000009835102 OI00000000000000000001 PI00000000000000000002 9888 CNY 3 Canceled
          OII0000000000009997401 OI00000000000000000002 PI00000000000000000003 7888 CNY 1 Completed
          OII0000000000009998702 OI00000000000000000003 PI00000000000000000003 7888 CNY 1 Canceled
        4. 实体表关系说明

          Order表同OrderItem为父子表,通过OrderID进行主外键关联;Customer表同Order表为父子表,通过CustomerID进行主外键关联;Product表同OrderItem表为父子表,通过ProductID进行主外键关联。

        5. 用户自定制

          用户有执行DDL权限,可以在自己租户数据库内在进行扩展模型自定义,建立自定义的物理表,索引,关系等。

        6. 问题和风险

          1. 用户具有执行DDL权限,可以自定义数据库物理模型,会带来各租户的自定义数据模型大爆炸,会给后续平台模型定义升级冲突,造成模型升级的巨大的障碍
          2. 同时由于系统标准模型和用户模型均为物理模型,未有做系统标准和自定义数据的有效隔离,如何保证平台应用的每一次升级必然会考虑对现有用户自定义模型的稳定性和可用性的影响,在自定义物理模型的情况下,不仅挑战巨大,而且包含巨大的回归验证的工作量,很难收敛。
          3. 当用户执行DDL时,通常会锁定数据库物理资源,当数据库数量非常巨大时可能会带来不可控的downtime,对应用系统的可用性造成加大影响。如果数据库是每个租户各自独占的还只会影响到单个租户,但是如果是多租户共享数据库,则可能会影响到其他租户,影响是灾难性的。作为云平台服务商,不管是用户操作还是系统行为,我们都不期望我们的设计对用户系统的可用性造成影响,所以用户执行DDL的行为是否允许确实有待商榷,但是如果不允许,用户可扩展性在这种设计环境中必然受到一定程度的限制。

           

      2. 元数据驱动多租户数据模型(Metadata Tables)

        前面章节描述了元数据驱动的多租户模型简单模型图,本小节详细解说下每个核心实体表的核心结构,同时已知资料部分较为简略,无法描述模型全貌和核心细节,为了模型完整性,整体数据模型包含了个人思路推理部分,用以完整清晰定义模型。当然由于所有模型都是subjective的,仅代表个人观点,欢迎大家的不同的观点,一起讨论改进。

        正如前面”一个云平台“的提到的通过一个统一的数据库来支撑无数个租户,所以元数据驱动的多租户模型是基于一个共享数据库的前提下。当然多租户实现设计多种多样,大家可以不拘泥此种。

        1. 元数据表之对象定义表:Objects表****

          image-20190826222843944.png

          Object系统表存储了每个租户为它的扩展应用对象定义的元数据,包含如下核心字段:

          1. ObjID:应用对象唯一标识,具有固定长度和格式。
          2. OrgID: 应用对象所归属的租户ID,用于统一共享数据库内的多租户数据隔离,通常和租户定义的域名对应。
          3. ObjName/Name:对象名称,用于系统配置和开发(developer name)。
          4. Label: 对象的显示名称。

          除了用户自定义对象,系统的标准对象也是采用相同的方式进行定义的。

        2. 元数据表之字段与关系定义表:Fields表

           

          image-20190826230914379.png

           

          Fields系统表存储了每个租户为他的扩展应用对象字段定义的元数据,包含了其所归属的应用对象的租户OrgID,字段所属对象的ObjID,字段定义标识FieldID,字段名称FieldName,字段存储位置定义FieldNum,数据类型DataType,数据类型重要关联字段(DigitLeft,Scale,TextLength,RelatedTo,ChildRelationshipName),以及是否必选、唯一、索引标记,还有部分标准字段。Fields表非常关键,其不仅定义了普通的应用对象字段定义包括基本信息和数据类型信息,而且通过特殊关系字段对不同应用对象之间的关系进行了定义。下面进行详细说明:

          1. FieldID : 此对象字段的唯一标识,具有固定长度和格式

          2. OrgID : 其所归属的应用对象所归属的租户OrgID

          3. ObjID :字段所属对象的ObjID

          4. FieldName/Name : 字段名,用于系统配置和开发(developer name)。

          5. Label : 字段展示名称,用以展示给最终用户。

          6. FieldNum :对应到Data数据表的数据存储字段映射,暨Data表中ValueX字段中的X。

          7. DataType:指定此对象字段的数据类型包含普通类型:Number、TEXT、Auto Number、Date/Time、Email、Text Area等,也包含特殊的关系类型如:Look up关系类型、Master-Detail关系类型等。

          8. DigitLeft和Scale:用于Number、Currency、Geolocation等数字数据类型的关联设定,例如定义了一个字段的DateType为Number,则需要指定其整数部分的最大位数DigitLeft和小数部分的最大位数Scale,两部分长度总和不超过18位。

          9. TextLength:当数据类型为TEXT时启用,用于指定TEXT类型的字符的长度限制。

          10. RelatedToChildRelationshipName:这两个字段当DateType为关系类型(Look up,Master-Detail等)时会启用,其中RelatedTo保存关联的应用对象ID,ChildRelationshipName用于保存父子关系中子方的关系名称,同一个父对象的子方的关系名称唯一,用于关系的反向查询。

          11. IsRequired:此字段数据保存时,是否校验值的存在

          12. IsUnique: 是否允许重复值

          13. IsIndexed:此字段是否需要建索引

          14. 其他字段:此处仅列举了说明模型所需要的字段,其他字段暂不进行列举,不列举原因和其重要性并无直接关联。

             

             

        3. 数据表(Data Tables)之关系数据表:Data表

          image-20190826234416735.png

           

          MT_Data系统表存储了MT_Objects和MT_Fields元数据表内定义的数据对象(表)所对应的数据,一一映射到不同的租户各自定义的表和表中的字段(对象和对象字段)。

          1. GUID:数据表的主键,用于存放每个应用对象实例的标识ID。
          2. ObjID: 其所归属的应用对象所归属的租户OrgID
          3. Name: 应用对象实例名称
          4. Value0....Value500:用于存放对象实例字段的数据,其ValueX中X值对应到Fields表中FieldNum定义,ValueX存放的数据,不管原始数据类型,存储格式均为变长字符串格式。

           

        4. 数据表(Data Tables)之非结构化数据表:CLobs

          MT_Clobs用于存储大字符段的存储CLOB,同时CLOB的也存储在数据库外的索引结构中,用于快速的Full-Text文本检索。

      3. 元数据模型核心实体关系图

        我们在应用系统开发中,通常我们定义的数据结构包括数据表、表字段,索引通常都会直接定义在物理数据库中,创建物理的表和字段以及索引等。

        但是在元数据驱动平台数据模型中,我们定义的用户表包括系统表都是逻辑表,其结构是虚拟的,用户表的定义存储在Objects表,对应的字段定义存储在Fields表中,实际用户数据存储在Data表中,特别注意的是,对象的引用关系定义也定义在Fields表中,以特殊数据类型方式来定义。(另:Relationships表后面章节进行描述)。

        从每个租户视角来看,每个租户都在一个共享数据库内拥有一个基于租户标识OrgID来隔离的虚拟的租户数据库。

        元数据实体包括Objects和Fileds实体以及实际数据Data实体都包含租户OrgID,这样就可以通过租户OrgID来天然隔离各租户的数据,当然不止这些实体,包括索引相关等透视表实体也使如此。

        image-20190826235641033.png

         

      4. 标准对象与标准字段

        前面整体架构层次里提到了公共元数据层和标准应用层,公共元数据层提供了标准对象和标准字段的定义。

        其中标准对象为每个租户提供公共端到端的应用的标准应用功能。

        Standard Objects
        Account
        Contact
        Lead
        Opportunity
        Case

        同时用户可以在标准的对象基础上扩展自定义的应用对象,满足自己的特定业务场景。__c后缀代表自定义,后续详解。

        Custom Objects
        Product__c
        Customer__c
        Order__c
        OrderItem__c
         ...

        而标准字段则提供给每个对象包括自定义对象的共同的字段,包含部分业务字段和非业务字段。

        Standard Fields
        ID
        Name
        CreatedBy
        CreatedDate
        LastModifiedBy
        LastModifiedDate
        OwnerID
        IsDeleted

        用户也可以在标准对象和自定义对象内自定义不同的字段,以满足业务需要。__c后缀代表自定义,后续详解。

        Custom Fields
        First_Name__c
        Last_Name__c
        Nick_Name__c
        Login_Name__c
        CustomeStatusc
        Product_Status__c
        Product_No__c
        Order_No__c
        ItemID__c
        Item_Quantity__c
        Order_Time__c
      5. 对象关系类型

        应用对象关系类型主要分为Look up 和 Master-Detail两种关系类型,其中Look up为弱的父子关系类型,Master-Detail为强的父子关系类型,其特性对比如下。

          关系类型  
        特性 Look up Master-Detail
        1.非空性 可选 不可以
        2.删除行为 方式可选:Clear/Block/Cascade Cascade级联
        3.记录关系 相互独立父子关系 不可分父子关系
      6. 元数据驱动的多租户数据架构示例

        同样采用普通多租户SAAS数据架构方案中相同的域模型和示例数据作为参照进行说明,只不过在这里域模型不再对应到数据库的物理模型,而是对应到元数据所定义的虚拟数据库的逻辑模型。请前后对比两种模型对用户业务模型承载的差异和联系,以便深入了解元数据驱动的多租户数据架构。

        image-20190826165345474.png

        对于Tenant租户 A00001,需要支撑相同的业务逻辑,需要定义相同的域模型,和普通的方案不同的是,这里采用元数据驱动的多租户数据模型来定义订单域模型和对应示例数据,其中域模型定义在元数据表(Metadata Tables)中,数据存储在Data Tables表中。

        1. 用户自定义对象Product的定义

          Product对象的基本信息定义在Objects表,作为Objects表的一条记录,通过OrgID进行不同租户数据隔离。Object中的每一条记录都代表一个不同的对象。Objects表的定义非常清晰,这里不做过多的解释,请参考Objects表介绍。

          Objects        
          ObjID OrgID ObjName ObjLabel Description
          01I2v000002zTEU A00001 Product Product Product

          Product对象的字段结构定义在Fields表,同时通过ObjID同Order对象定义进行关联,通过OrgID进行多租户数据隔离。

          FieldID格式为字段定义的标识ID,用于区分每个字段定义,对于标准字段,则采用标准字段ID,如Name,则直接采用Name作为字段标识ID,对于自定义字段,则元数据引擎自动生成15位的标准格式的FieldID。其他字段定义请参考前面的Fields元数据表详细介绍。

          下面详细描述一下Product对象中每个字段定义:

          1. 产品名称Name 字段 为标准字段,数据格式为TEXT,长度为80
          2. 产品编号ProductNo 为自定义字段,数据格式为TEXT,长度为22,FieldNum为1对应Data表存储字段Value1,存储格式为变长字符串。
          3. 产品价格ProductPrice为自定义字段,数据格式为Currentcy(此格式类似Number,不同是带币种),整数最大长度DigitLeft:16位,小数位最大精度Scale:2位,FieldNum为2对应Data表存储字段Value3,存储格式为变长字符串。。
          4. 状态ProductStatus为自定义字段,数据格式为TEXT,长度为20,FieldNum为3对应Data表存储字段Value3,存储格式为变长字符串。

           

          Fields                              
          FieldID OrgID ObjID FieldName FieldLabel FieldNum DataType DigitLeft Scale TextLength RelatedTo ChildRelationshipName IsRequired IsUnique IsIndexed
          Name A00001 01I2v000002zTEU Name Product Name (标准字段此项无) TEXT     80     Y      
          00N2v00000OHwTL A00001 01I2v000002zTEU ProductNo Product No 1 TEXT     22     Y Y Yes  
          00N2v00000OHwTQ A00001 01I2v000002zTEU ProductPrice Product Price 2 Currency 16 2       Y      
          00N2v00000OHwTV A00001 01I2v000002zTEU ProductStatus Product Status 3 TEXT     20     Y      
        2. 用户自定义对象Customer的定义

          Customer对象的基本信息定义在Objects表,作为Objects表的一条记录,通过OrgID进行不同租户数据隔离。Object中的每一条记录都代表一个不同的对象。Objects表的定义非常清晰,这里不做过多的解释,请参考Objects表介绍。

          Objects        
          ObjID OrgID ObjName ObjLabel Description
          01I2v000002zTEZ A00001 Customer Customer Customer

          Customer对象的字段结构定义在Fields表,同时通过ObjID同Order对象定义进行关联,通过OrgID进行多租户数据隔离。

          下面详细描述一下Customer对象中每个字段定义:

          1. 用户名称Name,必选标准字段,不过多解释
          2. 用户编号CustomerNo为自定义字段,数据类型为TEXT,长度为22,FieldNum为1对应Data表存储字段Value1,存储格式为变长字符串。
          3. FirstName和LastName为自定义字段,数据类型为TEXT,长度均为20,FieldNum为2,3对应Data表存储字段Value2和Value3,存储格式为变长字符串。
          4. 用户昵称Nick Name为自定义字段,数据类型为TEXT,长度均为20,FieldNum为4对应Data表存储字段Value4,存储格式为变长字符串。
          5. 用户登录名LoginName为自定义字段,数据类型为TEXT,长度均为20,FieldNum为5对应Data表存储字段Value5,存储格式为变长字符串。
          6. 用户状态CustomerStatus为自定义字段,数据类型为TEXT或者PickList,长度为20,FieldNum为6对应Data表存储字段Value6。为简化起见,状态字段暂定义为TEXT,对应Data表存储字段Value4,存储格式为变长字符串。
          Fields                              
          FieldID OrgID ObjID FieldName FieldLabel FieldNum DataType DigitLeft Scale TextLength RelatedTo ChildRelationshipName IsRequired IsUnique IsIndexed
          Name A00001 01I2v000002zTEZ Name Customer Name (标准字段此项无) TEXT     80     Y      
          00N2v00000OHwmn A00001 01I2v000002zTEZ CustomerNo Customer No 1 TEXT     22     Y Y Yes  
          00N2v00000OHwms A00001 01I2v000002zTEZ FirstName First Name 2 TEXT     20     Y      
          00N2v00000OHwmx A00001 01I2v000002zTEZ LastName Last Name 3 TEXT     20     Y      
          00N2v00000OHwoF A00002 01I2v000003zTEZ Nick Name NickName 4 TEXT     20     Y      
          00N2v00000OHwoK A00003 01I2v000004zTEZ LoginName Login Name 5 TEXT     20     Y      
          00N2v00000OHwoP A00004 01I2v000005zTEZ CustomerStatus Customer Status 6 TEXT     20     Y      

           

        3. 用户订单Order逻辑表的定义

          Order对象的基本信息定义在Objects表,作为Objects表的一条记录,通过OrgID进行多租户数据隔离。Objects表中的每一条记录都代表一个不同的对象。

           

          Objects        
          ObjID OrgID ObjName ObjLabel Description
          01I2v000002zTEj A00001 Order Order Order

          Order对象的字段结构定义在Fields表,同时通过ObjID同Order对象定义进行关联,通过OrgID进行多租户数据隔离。

          下面详细描述一下Order对象中每个字段定义:

          1. 订单编号OrderNo为自定义字段,DataType数据格式为TEXT,长度为22,FieldNum为1,对应Data表存储字段Value1,存储格式为变长字符串。
          2. 关系字段Customer为自定义关系字段,DataType类型为弱类型Look up 关系,关联到父对象Customer,则RelatedTo列存储Customer的ObjID:01I2v000002zTEZ,对应的FieldNum为2,则Customer对象实例GUID存储在Data表的Value2列。ChildRelationshipName列存储对象父子关系中子关系名称:orders,用于对象关系中从父对象实例数据反查子对象实例数据。
          3. 订单状态OrderStatus为自定义字段,DataType类型为TEXT,长度为20,FieldNum为3,则状态存储在Data表的Value3列。为简化起见,状态字段暂定义为TEXT。
          4. 下单时间OrderTime为自定义字段,DataType类型为Date/Time,FieldNum为4,则下单时间存储在Data数据表的Value4列。
          Fields                              
          FieldID OrgID ObjID FieldName FieldLabel FieldNum DataType DigitLeft Scale TextLength RelatedTo ChildRelationshipName IsRequired IsUnique IsIndexed
          00N2v00000OHwsb A00001 01I2v000002zTEj OrderNo Order No 1 TEXT     22     Y Y Yes  
          00N2v00000OHwsl A00001 01I2v000002zTEj Customer Customer 2 Lookup       01I2v000002zTEZ orders Y   Yes  
          00N2v00000OHwt0 A00001 01I2v000002zTEj OrderStatus Order Status 3 TEXT     20     Y      
          00N2v00000OHwtt A00001 01I2v000002zTEj OrderTime Order Time 4 Date/Time           Y      
        4. 用户订单行OrderItem逻辑表定义

          同样的,OrderItem对象的基本信息也以一条记录的信息定义在Objects表,通过OrgID进行多租户数据隔离。Objects表中的每一条记录都代表一个不同的对象。

          Objects        
          ObjID OrgID ObjName ObjLabel Description
          01I2v000002zTEo A00001 OrderItem Order Item Order Item

          OrderItem的字段结构也定义在Fields表,通过ObjID同OrderItem对象关联,通过OrgID进行多租户数据隔离。

          下面详细描述一下Order对象中每个字段定义:

          1. 关系字段Order为自定义关系字段,DataType类型为强类型的Master-Detail关系,关联到父对象Order,则RelatedTo列存储Order对象的ObjID:01I2v000002zTEj,对应的FieldNum为1,则Order对象实例GUID存储在Data表的Value1列。ChildRelationshipName列存储对象父子关系中子关系名称:OrderItem(s),用于对象关系中从父对象Order实例数据反查子对象实例数据。
          2. 关系字段Product为自定义关系字段,DataType类型为弱类型的Look up关系,关联到父对象Product,则RelatedTo列存储Product对象的ObjID:01I2v000002zTEU,对应的FieldNum为2,则Product对象实例GUID存储在Data表的Value2列。ChildRelationshipName列存储对象父子关系中子关系名称:OrderItem(s),用于对象关系中从父对象Product实例数据反查子对象实例数据。
          3. 商品实际售价ItemPrice为自定义字段,DateType类型为Currentcy(此格式类似Number,不同是带币种),整数最大长度DigitLeft:16位,小数位最大精度Scale:2位,FieldNum为2对应Data表存储列Value3,存储格式为变长字符串。
          4. 商品购买数量Item Quantity为自定义字段,DataType类型为Number,整形长度为18位,无小数位数,FieldNum为4,对应Data数据表存储列Value4。
          5. 订单明细状态OrderItemStatus为自定义字段,Datetype类型为TEXT,长度为20,对应FieldNum为5,对应Data数据表存储列Value5。为简化起见,状态字段暂定义为TEXT。
          Fields                              
          FieldID OrgID ObjID FieldName FieldLabel FieldNum DataType DigitLeft Scale TextLength RelatedTo ChildRelationshipName IsRequired IsUnique IsIndexed
          00N2v00000OHwtF A00001 01I2v000002zTEo Order Order 1 Master-Detail       01I2v000002zTEj OrderItem Y      
          00N2v00000OHwtZ A00001 01I2v000002zTEo Product Product 2 Lookup       01I2v000002zTEU OrderItem Y      
          00N2v00000OHwte A00001 01I2v000002zTEo ItemPrice ItemPrice 3 Currency 16 2       Y      
          00N2v00000OHwtj A00001 01I2v000002zTEo ItemQuantity Item Quantity 4 Number 18 0       Y      
          00N2v00000OHwto A00001 01I2v000002zTEo OrderItemStatus Order Item Status 5 TEXT     20     Y      
        5. 对象Schema

          定义好的用户应用对象Schema如下图

          image-20190827220229546.png

           

        6. 数据表Data表用户数据存储

          前面提到了用户自定义的应用对象以虚拟结构的方式存储在Objects和Fields表中,那么用户定义的应用对象Product、Customer、Order和OrderItem里的数据存储在哪里呢?答案是Data表,用户定义的对象的数据均会存储在Data表中,每个用户定义对象实例(或者近似称为用户表记录)数据以Data表中一条记录的形式存在。Product、Customer、Order表的数据记录均存储在Data表,OrderItem也亦是如此。

          其中,GUID作为每条数据记录暨是每个对象实例的全局唯一标识,OrgID进行多租户数据隔离,ObjID同Objects表关联代表具体哪个对象定义。

          这里重点提一下,Fields中定义的对象字段在Data表中的存储,其中Fields表中FieldNum非常关键,它对应了对象实例字段在Data表中的具体存储位置,FieldNum对应数字决定着数据存储在Data表中的哪个ValueX列。前面每个对象结构定义都对FieldNum对应Data的进行了说明,对象字段FieldNum可以不按照顺序来,只要FieldNum没有占用,可以任意对应,当然按照顺序是比较好的实践。

          再举例来说:

          1. Order对象的Customer关系字段定义在Fields表中,其FieldNum为1,则其在Data表中存储的位置,就是是Order对象实例在Data对应的记录中Value1这个字段所存储的值,存储的值为Customer对象实例GUID,也就是:a062v00001YXEKuAAP、a062v00001YXEKzAAP等。
          2. OrderItem对象的Product、ItemQuantity字段定义在Fields表中,其对应的FieldNum分别为2、4,则其在Data表中存储的位置,就是OrderItem对象在Data对应的记录中Value2、以及Value4所存储的数据,也就是:a052v00000jbgEQAAY、2以及a052v00000jbgMqAAI、3等记录。
          Data                            
          GUID OrgID ObjID Name Value1 Value2 Value3 Value4 Value5 Value6 Value500 CreatedBy CreatedDate
          a052v00000jbgEQAAY A00001 01I2v000002zTEU IPhone8 256G Golden PI201901060930A0000001 6000 Online                
          a052v00000jbgMqAAI A00001 01I2v000002zTEU IPhoneX 256G Golden PI201901060930A0000002 10000 Online                
          a052v00000jbgMvAAI A00001 01I2v000002zTEU IPhoneXR 256G Golden PI201901060930A0000003 8000 Online                
          a062v00001YXEKuAAP A00001 01I2v000002zTEZ Cheng Yan CI200903091014A0000001 Yan Cheng cy chengyan Valid          
          a062v00001YXEKzAAP A00001 01I2v000003zTEZ Ling Jun CI200903091014A0000002 Jun Ling lj lingjun Valid          
          a062v00001YXEL0AAP A00001 01I2v000004zTEZ Tommy Valdels CI200903091014A0000003 Tommy Valdels tom Tommy Valid          
          a062v00001YXEL1AAP A00001 01I2v000005zTEZ Dorothy Franklin CI200903091014A0000004 Dorothy Franklin doro Dorothy Valid          
          a072v000016DxY0AAK A00001 01I2v000002zTEj Order ON201903091914A0000001 a062v00001YXEKuAAP Completed 2019-08-01T04:00:00.000+0000              
          a072v000016DxYPAA0 A00001 01I2v000002zTEj Order ON201903091914A0100003 a062v00001YXEKzAAP Completed 2019-03-09T11:00:00.000+0000              
          a072v000016DxYFAA0 A00001 01I2v000002zTEj Order ON201903091914A0200005 a062v00001YXEL1AAP Completed 2019-03-08T23:30:00.000+0000              
          a082v00002mD8EoAAK A00001 01I2v000002zTEo Order Item a072v000016DxY0AAK a052v00000jbgEQAAY 5888 2 InProduction            
          a082v00002mD8EpAAK A00001 01I2v000002zTEo Order Item a072v000016DxY0AAK a052v00000jbgMqAAI 9888 3 Canceled            
          a082v00002mD8EtAAK A00001 01I2v000002zTEo Order Item a072v000016DxYPAA0 a052v00000jbgMvAAI 7888 1 InTransfer            
          a082v00002mD8EuAAK A00001 01I2v000002zTEo Order Item a072v000016DxYFAA0 a052v00000jbgMvAAI 7888 1 InTransfer            
                                     
                                       
      7. 通用的存储,按需转换—Data表数据类型与存储

        我们看了元数据驱动的多租户模型的核心关系,明白了用户自定义表(包括应用系统表)以及表结构是在Objects和Fields进行虚拟定义的,也清楚的知道了系统以及用户表的数据是作为一条条记录存储在Data表中的,那么我们下面来看下不同的数据类型如何在Data中进行存储的呢?

        在Fields表中,可以采用任何一种标准的结构化的数据类型,如text,number,date,以及date/time对用户表字段进行定义,也可以采用特殊结构的数据类型对字段类型进行定义,如下拉框picklist,系统自增字段autonumber,公式列(只读的公式推导列),布尔多选框,email,URL以及其他的类型,当然也可以通过系统应用来对Fields中的自定义字段进行强制约束包括是否必须非空以及掐校验规则(如符合特定格式,符合特定值范围等)。

        上述的各种不同字段格式数据都是存储在Data表中的ValueX列中的,Data表中包含500个数据列,称为弹性列,用来存储用户数据和系统数据,也就是对应到Objects表和Fields表对应的虚拟表结构所要承载的数据。

        特别的,所有弹性列都用了一个可变长度的字符串类型,以便于他们可以存储任何结构化类型的应用和用户数据(字符串,数字,日期等)。

        正是因为弹性列把所有不同的数据类型拉平来存储,所以任一弹性列可以对存储任何对象的任何类型的属性来存储,用户可以指定不同的对象的不同属性对应的不同的存储弹性列,当然同属于相同对象的实例的属性对应的弹性列是一致的。一个弹性列可以存储来不同的格式的数据,前提条件是这些数据属于不同的对象的不同属性。例如:上一节示例中,Data表的Value2列可以存储Order表的日期格式的OrderTime数据,也可以存储OrderItem表的格式为字符串的OrderID数据。

        image-20190822213649115.png

        如上所述,弹性列用通用数据类型暨可变长字符串来存储所有类型的数据,这样就可以在不同的用户表字段间共享相同弹性列,即便它们的数据类型各异。

        既然所有的数据全部用通用的可变长字符串来存储,那么应用逻辑处理需要不同的数据格式时候怎么办呢?具体做法如下:

        当应用系统需要从弹性列读取和写入数据时候,UDD(Universal Data Dictionary)层暨元数据运行引擎会用底层数据库系统数据转换函数(如Oracle数据库的TO_NUMBER,TO_DATE,TO_CHAR函数)按需对数据格式进行转换,将字符串格式转换成对应的数据格式(数字,日期等)。

        如果存储非结构化的大文本块数据怎么办呢?模型支持对Clob大字段的定义,对于在Data表中具有CLob数据的每一行数据,系统将其存储在Clobs透视表中,并按照需要同Data表的对应数据对象实例记录进行关联。

      8. 多租户索引透视表(Pivot Tables)

        1. Indexes透视表

          大多数结构化的数据存储在Data表内,如前面提到的,所有这些不同类型数据都是以可变字符串的形式存在ValueX列里面如各种数字以及日期等全部都是以可变字符存储的,这样虽然对于对象实例各种字段的存储确实非常灵活,不同的列可以存储不同类型的数据,即使同一ValueX列不同的对象也可以存储类型的数据,但是这样带来一个巨大的问题,由于不同的数据类型以可变字符串的方式存储在同一列内,你没办法利用底层数据库索引的能力对其进行排序,ValueX列的数据都是一种按照离散的顺序来存储的。传统的数据库依赖原生的数据库索引来快速在数据表内定位到符合查询条件的记录。而按照Data表ValueX列的数据存储情况,在Data表建立ValueX列的索引来支撑数据快速查询是不现实的。

          所以解决办法就是建立另外的透视表叫做Indexes索引表,并把数据拷贝出数据表并转换成原始的的数据类型,并存储到Indexes索引表列内,如原来是整形的数据以可变字符串的格式存储在ValueX列中,拷贝到Indexes表之前通过函数将其转换为原始的数据类型,在存储到Indexes对应的NumValue列内,以方便建立索引,Indexes表包含强类型的索引类,像StringValue,NumValue,DataValue,用来定位对应数据类型的字段数据。

          image-20190827224554239.png

          Indexes透视表的字段说明如下:

          1. OrgID:其所归属的应用对象所归属的租户OrgID
          2. ObjID:字段所属应用对象唯一标识
          3. FieldNum:对象字段存储位置
          4. ObjInstanc

2 Comments

  • xpc

    作者是已经做了这个系统还是仅仅是研究这个领域呢?

    另外有的salesforce的介绍里字段名可能不太一样。
    OrgID: org-1
    ObjID: prod-1
    FieldID: field-1
    Name : Price
    DataType : Decimal
    IsIndexed : False
    RelativePosition : 1

  • xpc

    ChildRelationshipName 是否有必要性?

    引用字段,存储了relatedTo 表示从这个字段到父表字段的引用就足够了,不明白为什么需要ChildRelationshipName?关系的名称,可以通过objxx1的valuexx1到objxx2的信息中组合得到。不需要定义一个名字吧。

    salesforce里有mt_relationships 透视表,里面包含做了联合主键的两列数据,来方便进行外键关联的检索。但应该不是ChildRelationshipName

Leave a Reply

Your email address will not be published. Required fields are marked *