技术分享 | 业务模板的技术实践

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了技术分享 | 业务模板的技术实践相关的知识,希望对你有一定的参考价值。

源宝导读:“业务模板”作为天际·建模平台3.0推出的重要特性,它将元数据复用发挥到了极致,通过业务模板几乎可以覆盖整个建模元数据开发流程,提供业务场景级别的复用能力。本文将介绍“业务模板”的设计原理、实现方案和应用场景。

一、背景

    建模平台作为天际开放平台的核心能力域之一,是一个极致高效的低代码开发平台,支持通过数据建模、页面建模、组件建模等可视化建模方式低代码或零代码快速构建企业级应用。

    使用建模平台开发应用的过程一般是:数据建模=》页面建模=》多个页面组合成业务场景。数据模型可以被多个页面复用,组件模型也可以被多个页面复用,通过建模平台可以快速构建出相应的场景。

    例如:开发一个简单的用户管理系统,管理用户名称和用户编码信息。

    首先进行数据建模:我们可以设计一个用户表,包含用户名称和用户编码两个字段。然后进行页面建模:创建一个用户表单页面,使用用户表作为数据源,表单使用输入框组件绑定用户名称和用户编码,设置相应的输入格式和校验规则,就可以实现用户名称和用户编码的录入功能了;再创建一个用户列表页面,使用用户表作为数据源,使用用户名称和用户编码字段作为列表展示字段,就可以批量展示和管理用户数据了。

    完成这么一个简单的用户表单录入+列表展示数据场景,使用建模平台只需要花费几分钟就能完成。

    如果现在需要再开发一个客户管理系统,管理客户名称和客户编码信息。正常的做法就按照上面的建模流程就行数据建模(客户表)=》页面建模(客户表单、客户列表)=》客户管理系统。

    分析用户管理系统和客户管理系统我们会发现这两个业务场景极其类似,都是针对名称和编码(唯一标识)的管理,但是都要进行数据建模(数据隔离,表名不同,字段名一致),页面建模(结构一致,只是展示的文本描述不同)。虽然重复完成这个场景只需要几分钟,但是如果每个客户都有类似的场景,那么都需要花费几分钟;如果场景再复杂一点,涉及到多个数据源,多个页面呢,每个页面又用到复杂的组件或者功能特性,那么完成一个类似的场景需要花费多少时间呢?

二、什么是业务模板

    通过分析以上的场景我们发现,目前建模平台提供的主要是基于组件级和页面级的复用能力,针对业务场景复用能力比较薄弱。而实际产品场景中确实存在这种业务功能极其类似的场景,比如成本系统的变更业务场景,设计变更和现场签证两个具体的变更过业务十分相似,如果重复开发的话需要耗费大量的开发资源。

    为了解决这种业务场景级别的复用能力,在ERP3.0版本中,建模平台提供了业务模板功能。基于业务领域抽象的模型,提炼出一套业务模板,通过模板化的思路快速复制的方式构建有规律、符合模型的业务。

    目前建模中业务模板覆盖了大部分建模能力,从数据建模到页面建模、业务组件、业务参数以及主页、标签页和权限,以支持创建一个业务场景闭环。

三、业务模板的原理

3.1、什么是元数据

元数据的定义:

元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。

    元数据在建模平台中的作用是描述建模中所有特性的数据,是整个建模平台的基石,建模平台中的一切模型都是以元数据作为数据载体,建模中的元数据物理表现形式为一个个XML文件。

    例如页面模型元数据,每个页面对应的就是一个XML文件。

    页面元数据:

<?xml version="1.0" encoding="utf-8"?>
<functionPage title="合同变更基本信息表单" pageName="合同变更基本信息表单">
    <pageLayout pageLayoutCategory="OneColumn">
      <cells><cell rowIndex="0" width="100%" height="100%">
        <control id="appForm" entityName="主实体"  autoHeight="1" />
      </cell></cells>
  </pageLayout>
</functionPage>

    根据不同的模型对应不同的元数据类型,分别存储在不同的文件夹下,各个元数据之间存在在互相引用的关系。比如页面引用数据表,主页引用页面等。

3.2、什么是页面模板

    建模通过可视化的方式创建建模模型,创建页面时,可以选择不同的页面模板,其中包含组合模板,单页模板等。

    选择一个列表+表单的组合模板,选择数据源后点击开始创建,创建成功以后我们会发现,在列表的设计器中,新增按钮默认会打开创建的表单页面:

    页面元数据是如何生成的?两个页面之间是如何关联起来的呢?

    首先看下元数据在整个应用中的存在形式如下:

    元数据在不同的场景对应不同的模型,在建模中对应建模模型,在程序内存中对应元数据模型,在文件中对应XML文件,在ERP中对应一个个真实的页面、数据表等,同时不同阶段的模型可以互相转换。

    建模中生成元数据的方式有两种:

  • 根据建模模型,先生成内存元数据模型,然后序列化到文件中;

  • 根据建模模型对应的文件模板,反序列化生成内存元数据模型,然后再序列化到文件中;

    第一种生成方式适合元数据结构比较简单的场景,比如数据表,视图,业务参数等。第二种方式适合结构复杂,存在默认节点或者默认值的场景,比如页面,表单,列表和数列表等。

    页面组合模板就是用的文件模板方式,将通用的文件结构提炼出来,生成模板,需要动态生成的数据使用关键字做占位符,在生成内存元数据模型前,将文件中的关键字根据上下文替换成对应的值。

页面模板:

<functionPage templateId="2C63E14E-FF7E-4E0B-AE15-FEFB8DA34213">
   <pageLayout pageLayoutCategory="OneColumn">
    <cells><cell rowIndex="0" width="100%" height="100%">
       <control id="[prop:setting.Customize]appGrid" 
          entityName="主实体"  autoHeight="1"/>
     </cell></cells>
   </pageLayout>
</functionPage>

    其中[prop:xxx]就表示关键字,在生成元数据模型时会进行替换。例如:parentPage=“[prop:page0.Name] ”表示表单页面的父级页面即返回上级页面为第一个页面也就是列表页面的地址,所以通过点击列表新增按钮进入表单页面后,点击上一级就能返回到列表页面了。

3.3、业务模板的原理

    页面模板解决的是快速创建页面的问题,业务模板要解决的问题是快速创建一类业务场景的问题。在技术实现上,业务模板也采用文件模板的方式,根据一个完整的业务场景所包含的所有的元数据文件,提炼出一套通用的元数据文件模板,根据这套文件模板可以快速生成不同的业务场景元数据。

    模板文件中包含静态和动态两类结构。静态指通用的结构,动态指根据不同的场景显示不同数据的结构。例如在一个页面模板中,页面都包含页面布局,那么页面布局就是一个静态结构;在不同的场景页面名称需要根据对应的场景显示,比如在用户管理界面,显示为用户页面,在客户管理界面,显示为客户页面,那么页面名称就是动态数据。

    那么如何生成动态数据呢?

    答案就是关键字,使用关键字作为动态数据占位符,然后根据模板上下文动态替换关键字。

下面以成本的变更申报表单页面模板为例:

<functionPage
functionPageId="{{$system.NewId}}" name="{{$system.NewId}}"
title="{{$global.name}}变更申报表单页面" 
pageName="{{$global.name}}变更申报表单页面" 
url="/std/02010350/{{$page.ReceiptsDeclare.name}}" 
description="{{$global.name}}变更申报表表单" >
    <pageLayout pageLayoutCategory="OneColumn" layoutType="0">
        <cells>            
           <cell id="45a3adeb-009f-ea11-86e2-94c6910421d9">
               <control
                  id="appForm"
                  type="Mysoft.Map6.Modeling.Controls.AppForm"
                  metadataId="{{$form.ReceiptsDeclareForm.formId}}"
                  entityName="主实体"
              />
            </cell>
        </cells>
</functionPage>

其中{{xxx}}就是关键字,根据不同的使用场景关键字可以分为以下几类:

1、系统关键字

    系统关键字为系统内置关键字,目前只有$system.NewId,作用是生成新的Guid。例如每个页面的元数据id应该唯一,因此模板中functionPageId="{{$system.NewId}}"。

2、全局关键字

    由用户生成模板实例时输入的业务标识和业务名称、以及当前环境上下文生成。例如页面名称应该跟业务场景有关,因此模板中pageName="{{$global.name}}变更申报表单页面"。

关键字

说明

$global.id业务的唯一标识
$global.name业务名称
... ...... ...

3、配置关键字

    根据模板配置生成配置界面,由用户选择的配置项生成。

关键字

说明

$config.[字段]创建实例时选择的配置值
$config.[字段].field配置对应的字段
... ...... ...

4、模板内容关键字

    元数据之间存在着各种联系,比如数据关系记录的是两个数据表之间的关系,标准图形化数据源页面引用了数据表作为数据源。因此模板文件不仅包含关键字,同时模板文件也作为关键字的提供者,提供关键字供其他模板引用。

类型

关键字

实体关键字$entity.[模板英文名称].[属性名称]
关系关键字$realtion.[模板英文名称].[RelationshipId]
... ...... ...

    在变更申报表单的业务模板中,由于引用了变更申报表单大控件,同时变更申报表单也是使用模板生成的,所以页面跟表单是引用的关系metadataId="{{$form.ReceiptsDeclareForm.formId}}" 。

3.4、什么是模板实例

    定义了业务模板以后就可以根据不同的业务场景动态替换模板文件中的关键字,从而生成一组有业务关联的元数据文件,我们将由同一个业务模板生成的不同业务场景的元数据称为业务模板的一个实例,业务模板实例元数据是描述这个实例的数据。

生成模板实例的流程如下:

四、如何开发业务模板

4.1、提炼业务模板内容

    根据已经存在的元数据文件提炼通用的业务模板,分清楚哪些是静态结构,哪些是动态结构,模板和模板之间是如何互相引用的。

    以下以开发一个客户表为例说明如何创建客户表模板:

    1、首先在建模中创建一个客户表(test_Customer),增加客户名称(Name)和客户编码(Code)两个字段,对应的元数据:

客户表”元数据:

<?xml version="1.0" encoding="utf-8"?>
<MetadataEntity EntityId="91c51e44-0bf2-4ef6-43bc-08d94ab36d6f" Name="test_Customer" DisplayName="客户表">
    <Attributes>
        <MetadataAttribute>
            <AttributeId>4cabeff5-0515-4920-6d86-08d94ab36d6f</AttributeId>
            <Name>Code</Name>
            <DbType>nvarchar</DbType>
            <Length>128</Length>
        </MetadataAttribute>
    </Attributes>
</MetadataEntity>

    2、分析元数据中的静态数据和动态数据,例如元数据的id和表名name要唯一,字段的id要唯一,因此这些不能重复的值就作为动态数据局,要使用关键字替换;不同数据表的字段的名称可以一样,因此数据字段名称可以作为静态数据。

    3、使用关键字替换动态结构

客户表”元数据模板:

<?xml version="1.0" encoding="utf-8"?>
<MetadataEntity EntityId="{{$system.NewId}}" Name="test_{{$global.id}}" DisplayName="{{$global.name}}">
    <Attributes>
        <MetadataAttribute>
            <!-- 用表达式占位,生成随机GUID-->
            <AttributeId>{{$system.NewId}}</AttributeId>
            <Name>Code</Name>            
            <DbType>nvarchar</DbType>
            <Length>128</Length>
        </MetadataAttribute>
</MetadataEntity>

4.2、定义业务模板配置

    业务模板配置由:控件配置、布局配置和联动配置组成。

    控件配置定义控件和字段信息:

<configs>
  <!-- 将配置项定义成单选框-->
    <configItem itemId="AlterApply_IsInvalidCostEnable" label="启用无效成本" 
          field="AlterApply_IsInvalidCostEnable" control="Radio" dataType="String">
        <options>
            <option text="是" value="1" isDefault="true"/>
            <option text="否" value="0" isDefault="false"/>
        </options>
    </configItem>
    <configItem itemId="AlterApply_IsIdleCostSupplement" label="启用无效成本补录" 
         field="AlterApply_IsIdleCostSupplement" control="Radio" dataType="String">
        <options>
            <option text="是" value="1" isDefault="true"/>
            <option text="否" value="0" isDefault="false"/>
        </options>
    </configItem>
</configs>

布局配置关联控件配置从而定义布局显示:

<layout><regions><region title="阶段配置">
  <group title="申报阶段">
    <rows><row><cells>
      <!-- refld引用之前定义的两个配置项-->
      <cell colSpan="1">
        <configItem refId="AlterApply_IsInvalidCostEnable"/>
      </cell>
      <cell colSpan="2">
        <configItem refId="AlterApply_IsIdleCostSupplement"/>
      </cell>
    </cells></row></rows>
  </group>
</region></regions></layout>

联动规则定义配置之间的联动规则:

<rule itemId="AlterApply_IsIdleCostSupplement">
  <ruleConditions>
    <!-- 条件判断来自配置项的值-->
    <ruleCondition field="AlterApply_IsInvalidCostEnable" operator="eq" value="1"/>
  </ruleConditions>
  <ruleActions>
    <!-- 条件为true时执行显示动作-->
    <ruleAction type="trueAction">
      <props>
        <value>0</value>
        <visible>true</visible>
      </props>
    </ruleAction>
    <!-- 条件为false时执行隐藏动作-->
    <ruleAction type="falseAction">
      <props>
        <value>0</value>
        <visible>false</visible>
      </props>
    </ruleAction>
  </ruleActions>
</rule>

4.3、订阅业务模板事件

    在业务模板生成实例的过程中,产品需要扩展部分逻辑,比如成本合同变更生成实例以后,需要向流程中心发现业务数据。我们在模板操作过程中会发布事件,产品通过订阅对应的事件进行业务逻辑扩展。

事件名称

事件

说明

业务模板实例前事件TemplateInstanceCreatingEven
tData
订阅此事件可以在生成实例前修改配置参数等
业务模板实例初始化事件TemplateInstanceInitEventData订阅此事件可以获取实例的信息
业务模板实例删除事件TemplateInstanceDeleteEventData订阅此事件可以做一些业务数据清理的操作

    所有的事件都是派生自BusinessUnitEventData的类,通过事件总线发布事件。要订阅处理事件,就要实现BusinessUnitEventHandler抽象类。

事件订阅:

/// <summary>
/// 业务事件的抽象类
/// </summary>
/// <typeparam name="TBusinessUnitEventData"></typeparam>
public abstract class BusinessUnitEventHandler<TBusinessUnitEventData>
    : IBusinessUnitEventHandler<TBusinessUnitEventData>
    where TBusinessUnitEventData : BusinessUnitEventData
{
    /// <summary>
    /// 订阅者具体的业务逻辑。
    /// </summary>
    /// <param name="businessUnitEventData"></param>
    public abstract void HandleEvent(TBusinessUnitEventData businessUnitEventData);
}

五、如何使用业务模板

    业务模板的使用非常简单,用户不用关心业务模板的开发原理,只用关注相应的配置项的作用即可。

    例如使用成本系统合同变更业务模板,通过修改相应的配置项就可以生成一个重计量的业务实例:

    通过查看业务模板实例信息查看生成的元数据:

六、应用案例

    目前ERP产品中主要是成本系统和全域主数据应用了业务模板,成本系统开发了变更业务模板,同时产品出厂的时候会自带现场签证和设计变更两个模板实例。

    合同变更业务模板:

    模板实例:

    全域主数据目前也正在使用业务模板开发主数据模板,主要分为列表型主数据模板,用以扩展无层级类主数据,比如供应商和客户;以及层级型主数据,用以扩展层积累主数据,比如科目信息。

七、总结

    业务模板主要是解决同一类业务场景的快速构建问题,基于业务领域抽象的模型,提炼出一套业务模板,通过模板化的思路快速复制的方式构建有规律、符合模型的业务。

    但是目前模板的内容是基于元数据文件进行提炼的,元数据结构比较复杂,相互之间的关联密切,导致开发业务模板的门槛比较高,同时生成元数据的过程比较复杂,出现问题很难排查定位。

    未来,我们将考虑通过可视化的方式构建业务模板,降低开发难度,让业务模板被更多的开发者使用,发挥更大的价值。

------ END ------

作者简介

李同学: 研发工程师,目前负责天际·建模平台相关研发工作。

也许您还想看:

技术分享|Java SDK 动态类型

技术分享|NodeJS分布式链路追踪实现

更多明源云·天际开放平台场景案例与开发小知识,可以关注明源云天际开发者社区公众号:

【建模】文档服务提供高保真打印模式

明源云·天际硬核技术认可:获华为鲲鹏技术认证书

天际·开发者社区“重装发布”!

以上是关于技术分享 | 业务模板的技术实践的主要内容,如果未能解决你的问题,请参考以下文章

华为云官网前端的技术演进与低代码实践

Java技术jQuery自定义插件开发实践

业务技术协同线上化的硬盘式研发管理实践

2019年微服务实践第一课,网易&谐云&蘑菇街&奥思技术大咖深度分享

深度学习核心技术精讲100篇(四十四)-深度召回在招聘推荐中的挑战和实践

设计模式在外卖营销业务中的实践