Discuz! Board

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 537|回复: 0

[开发指南] Intializer使用指南

[复制链接]

11

主题

37

帖子

113

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
113
发表于 2020-11-5 14:21:57 | 显示全部楼层 |阅读模式
原文链接: https://app.yinxiang.com/fx/1d91a8b9-03a8-46dd-8322-f03e4641d6fe

Initializer 3.0 使用说明What's new in Initializer 3.0调整项目
  • entity节点sourcePath更名为source.
  • entity节点groupcondition更名为groupKeySelector.
  • entity节点废除sourceFilter. 由于source是qss, 需要filter行为的时候, 可通过source完成.
  • entity节点去除isNew, 需要保持原isNew=true语义的时候, 需要调整source, 确保值不为空.
  • entity节点的enable调整为qss表达式.
  • entity节点增加target模式和source模式选项. source模式下的行为机制保持和原来一致, target模式下, 规则执行引擎根据目标对象来切换上下文, 此时source相关选项仅影响数据上下文.
不兼容项目此处仅列出无法通过模块迁移和元数据迁移逻辑完成, 需要手工调整的项目.
  • isNew=true, 且source上下文为空的, 为了保持语义, 需要在source中添加一个`new()`. (目前单据新建, 调用初始化之前也会先实例化实体, 并作为target转入).
  • 脚本中用到parent的, 需要检查是否语义一致.
  • 对于原来调用中传入target的地方, 需要检查是否需要将跟节点改为target模式.
  • 新建统一采用target模式后, 子表新增的那种, 需要在source中补充`new()`才能保持原来的语义.

概述总体来说, Initializer的主要能力就是按照一定的规则对目标对象赋值, 赋值过程中, 可以直接设定值, 也可以根据某个数据源来决定值. 以下是初始化器的主要应用场景:
单据新建或编辑数据初始化是指为一份已有的数据按规则填写初始值. 例如:
  • 单据新建时, 调用初始化器, 初始化主表的默认值. 有时还需要增加若干默认费用项等子表内容.
  • 编辑单据时, 需要再次添加商品信息, 选择商品明细后, 调用初始化器映射成单据明细.
  • 单据中的商品, 需要根据某种合并规则, 合并后转入另一份商品明细.
  • 单据复制, 实例化实体后, 逐字段复制, 随后调用复制初始化器
上下游单据转换
  • 上游单据下推生成下游单据. 又包含以下几种情况:
    • 一个单据下推, 往往需要根据单据明细的某些特征拆分成多个下游单据;
    • 多个单据下推, 往往需要根据单据头或者单据明细的某些特征合并成一个下游单据;
    • 以上两种情况还常常同时出现.
  • 下游单据新建时, 从上游单据选择主表及明细, 接着调用初始化器转换成下游单据.
  • 下游单据某些情况下需要根据上游单据更新数据, ??
数据导入
  • excel数据上传并解析后, 调用初始化器, 根据excel数据源产生一个或者多个单据; !!
财务智能凭证
  • 根据核算规则从接口库数据产生凭证. 对某一条规则而言, 可能对数据源分组后按组产生规则;

Initializer通过提供源数据上下文, 目标数据上下文以及转换规则的三种概念抽象来满足以上各种应用场景.
说明这三个概念之前, 我们首先对前面提到的应用场景做一些分析. 首先来看看各种场景下我们希望得到什么结果数据, 为达到这个目的, 需要提供哪些其他数据.

单据新建-初始化
完整的当前单据实体, 一条;
无;
单据编辑-选择商品
单据明细实体, 多条;
选中的商品实体, 多条;
单据编辑-合并转入
合并后的单据明细实体, 多条;
合并前的单据明细实体, 多条;
单据下推-一拆多
完整的下游单据实体, 多条;
完整的上游单据实体, 一条;
单据下推-多并一
完整的下游单据实体, 一条;
完整的上游单据实体, 多条;
单据下推-混合
完整的下游单据实体, 多条;
完整的上游单据实体, 多条;
下游单据转换
完整的下游单据实体, 一条;
选中的上游单据头实体, 多条;
选中的上游单据明细实体, 多条;
下游单据更新
完整的下游单据实体, 一条;
对应的上游单据头实体, 多条;
对应的上游单据明细实体, 多条;
数据导入
完整的单据实体, 多条;
excel数据实体, 多条;
智能凭证
完整的凭证分录实体, 多条;
规则对应的财务智能核算实体, 多条;
从上表可以看出, 虽然所有的场景简单来说都是根据一些数据产生另一些数据, 但由于两头数据都存在以下两个特点:
  • 源数据和目标数据可能是一条, 也可能是多条;
  • 源数据和目标数据可能是带有层级结构的实体, 也可能是平面二维数据表;
Initializer可以处理以上这些场景, 其主要挑战也在于此.

使用指南1. 简单初始化单据 v0.1让我们从一个简单的场景开始. 现在需要为一个销售订单实体提供初始值. 销售订单实体结构如下:
其中方块表示实体或者组合字段, 带竖线方块表示实体集合, 旗帜表示基本字段.

销售订单对应的初始化器如下图所示:
让我们一步步来分析这个初始化器.
根实体节点无论什么场景, Initializer的最终目标都是构建出目标实体, 因此初始化器的结构也是按照目标实体的结构来组织的. 实体结构可以用树形来表示, 因此初始化器的结构也是树形的.
每个节点包含了两方面的信息. 一个当然是目标实体中对应的位置; 另一个代表了为了产生目标实体对应位置的值所需的数据上下文, 俗称数据源.
例子中的根节点对应着目标实体的根, 也就是销售订单本身. 此外节点中的source=new()表示数据源为一个新建的空白实体, 其他属性可以暂时忽略. 这个节点规则执行的效果就是创建了一个默认的销售订单对象.
NOTE
并不是source属性的new()的值直接给了销售订单(事实上这是不可能的, 销售订单和new()类型不兼容), 目标实体的创建是初始化器引擎控制的, source属性的值仅仅影响数据源.

由于这个例子中所有的字段值都是常量, 和上下文无关, 因此source仅指定了一个简单对象.
字段规则第二个节点是字段规则. 它也包含两方面信息: 一个规则对应目标的销售订单实体中的签约日期字段; 另一个是字段内容产生规则为常量字符串HANGZHOU.
字段规则可以有很多类型, 常用的用常量, 脚本, 和数据源复制. 后文会陆续提到.
子实体节点由于销售订单中的客户是个组合字段, 带有子结构, 在实体模型中按照实体来对待, 因此第三个节点也是实体节点. 子实体节点和根实体节点没有本质区别.
例子中这个节点的作用也是告知初始化器引擎为销售合同.客户创建一个默认对象.

其他两个节点暂时没有用到, 先跳过.

这个初始化器执行完毕后, 我们会得到以下实体:
对应的数据库数据如下:
2. 从商品库选择商品前一步通过初始化器新建了一个销售订单, 接着我们要从商品库中选择商品添加到订单明细中. 下面是商品库的结构, 比较简单:
为此, 我们需要建立如下结构的初始化器:
规则上下文管道在这个例子中, 我们需要根据选择的商品行来映射生成订单明细, 因此根节点对应订单明细.
此外, 由于允许一次选择多条商品行批量添加到订单明细, 因此节点规则执行次数需要根据数据源的数量来确定.
节点规则执行一次就会产生一个目标实体, 而实体规则执行多少次取决于有多少个上下文, 因此, 我们首先需要了解实体节点上下文的构建过程.
实体节点中有source, groupMode, groupKeySelector, groupFilter, 这四个属性和上下文构建息息相关, 他们决定了上下文流水线的生产过程. 也就是规则上下文管道. 这个流水线管道有三道工序, 如下图所示:
  • 先对source表达式求值, 求值结果可以是普通对象, 也可以是一个数组. 如果结果是普通对象的话, 会包装成一个数组, 这个数组只有一个元素就是这个对象;
  • 然后根据groupModegroupKeysource的求值结果进行分组, 分组规则如下:
    • 如果groupMode=合并, 那么source数组中所有元素分成同一组, 该组的内容就是包含所有元素的数组;
    • 如果groupMode=拆分, 那么source数组中每个元组分成一组, 每组的内容是一个仅包含改组元素的数组;
    • 如果groupMode=普通, 则进一步对source数组中的每个元素执行groupKeySelector, 并根据执行结果分组, 每组的内容是一个包含具有相同groupKey的元素的数组;
  • 最后对每个分组执行groupFilter表达式, 如果表达式为false, 那么剔除这个分组.
至此, 分组数据组装完毕, 每个分组代表了一个上下文, 上下文内容就是分组内容, 所以每个上下文一定是一个数组, 只不过有时候这个数组只包含一条数据. 初始化引擎随后在每个上下文中执行一遍规则.
在这个例子中, 因为source的结果是包含三条商品的列表, 所以会执行三遍. 每次执行时候的上下文分别是包含这三条数据的数组. 过程如下:
第一步, 例子中的source=:sourceRoot表示该节点的数据来源切换为:sourceRoot,:sourceRoot是预定义的变量, 表示调用初始化器时传入的source参数, 如果选中了三条商品, 那么:sourceRoot为包含三条商品的列表.
第二步, 对三条商品进行分组, groupMode=拆分, 所以拆分成三组, 每组一条.
第三步, 对三个分组进行过滤, 这里没有设置过滤条件, 那么原样输出.
最后我们得到了三个上下文, 每个上下文包含一个数组, 每个数组中各有一条商品. 后续子规则会在每个上下文中各执行一遍, 最后产生三条订单明细.
copy规则回到例子, 让我们来看第二条规则:
这条规则的类型为copy, copy规则就是直接从上下文中获取指定字段的值.
这条规则的意思是从订单明细.名称字段从商品信息.名称字段取值.
再次强调下, 这条规则会在上述三个上下文中各执行一遍.
script规则script规则可以执行一段QSS表达式. 表达式的上下文就是规则上下文, 上下文中有两个预定义的变量, sf. s表示上下文数组, f表示数据中的第一条. 因为很多情况下, 数组中只有一条, 所以提供了f作为s[0]的快捷方式.
constant规则constant规则提供一个常量, 常量可以是字符串, 数值, 布尔值等.
3. 增强单据初始化规则之前的销售订单初始化规则很简陋, 甚至于还隐藏着一个和实际不符的地方, 让我们修复这个漏洞:
对比v0.1版的规则可以发现, 这套规则将根节点的mode改成了target.
实体节点的Mode在分析这些变化之前, 我们要先了解到Sailing框架的单据新建分几个阶段, 第一阶段是由框架新建实体对象, 并初始化一些由框架维护的内容, 第二阶段才是调用初始化器, 由规则来初始化业务字段. 也就是说, 在调用初始化器之前, 实体(至少是根实体)已经存在了, 初始化器不必新建根实体.
这一点与之前设置的规则其实是冲突的, 之前的规则在根节点会创建一个默认目标实体, 而这是不必要的, 甚至是错误的.
Initializer 3.0引入了mode的概念, 专门处理这类场景.
mode有两个选项, sourcetarget.
  • source模式下, 规则的执行次数和执行上下文是由规则上下文管道决定的;
  • 而target模式下, 执行上下文和执行次数取决于目当前节点所在目标数据. 如果目标数据是单个实体, 那么执行执行一次, 上下文就是目标实体本身; 如果目标数据是集合中的实体, 那么集合中有几个实体对象就执行几次, 每次执行的上下文就是该实体对象. target模式下, 还是可以设置source属性, 但此时不影响上下文.
为了修正这个问题, 我们需要将mode改成target
4. 再次加强单据初始化规则新建销售订单其实还需要默认带上若干常见费用, 让我们再次调整初始化规则.
实体集合节点其他费用是销售订单的子实体集合. 规则中对应的是实体集合节点(带竖线的),
实体集合节点仅仅是个容器, 用来将其下实体节点的生成结果合并成一个列表.
例子中集合节点下有两条实体规则, 每条实体规则的属性组合会产生一个上下文分组, 因此, 最后的结果会产生两条默认费用.
5. 产生下游单据前面的案例需要更加拓展一步, 增加一个报关单, 报关单的结构如下:
报关的数据来自销售订单, 并且有这么几个要求:
  • 报关单的商品明细从销售订单明细转过来, 但是需要根据根据品名合并;
  • 其他费用需要说明费用发生地;
对应的Initializer如下:
让我们来分析一下这套规则.
  • 根节点的source使用了:sourceRoot, :sourceRoot代表新建报关单是选择的销售订单. 值得一提的是销售订单可能有多个, groupMode=合并意味着无论有几个销售订单都合并产生一个报关单.
  • 报关明细节点的source=s.订单明细, s代表当前上下文中的数据数组, 此时还没有创建下层报关明细的上下文, 因此s为选择的订单列表. s.订单明细是QSS的向量化用法, 表示选中的订单中的所有订单明细. 这里groupMode=普通, 意味着需要根据groupKey分组, groupKey也是一个表达式, 它由一个特殊的上下文变量e, 代表了正在生成key的数据行,  groupfilter中也有类似的上下文变量.
  • 费用明细节点的source和报关明细类似, 但groupMode=拆分, 意味着为选中的订单中的所有其他费用每条产生一条费用明细.
  • 备注字段使用了QSS脚本规则, 脚本中使用到了一个特殊的标识符parent, 这个涉及到嵌套上下文的概念.
嵌套上下文我们已经知道了每个实体节点都创建一个上下文, 上下文中的s表示分组集合, f表示分组集合中的第一条.
如果一个初始化器存在嵌套的子实体节点, 类似上例中的报关明细, 费用明细, 那么这些节点创建的上下文也会嵌套起来, 子实体上下文中的规则可以使用parent标识符访问父实体上下文中的变量. 如下图所示:
上例中费用明细的备注字段规则为parent.f.签约地点, 含义是费用的备注统一取第一个报关单中的签约日期.
6. 加速规则执行之前从商品库选择上的映射规则还有两个费用没有考虑, 现在把这个完善上去.
加工费和包装费的规则都是脚本, 从商品信息中获取对应的费用并乘以折扣率. 但获取折扣率是个很耗时的过程, 上面的写法会导致获取每条商品获取两次折扣率, 三条商品就是六次, 而这六次的结果不会有什么不同. 如果能缓存这个结果就能提高规则的执行性能.
节点临时变量Initializer的实体节点提供了定义临时变量的方法, 对哪些耗时的操作, 可以在实体节点事先求值并存在放临时变量中, 然后在子规则中直接使用临时变量的中间结果.
修改后的规则如下:
实体节点中提供variables属性专用来定义变量. variables属性是个数组, 可以定义多个变量, name表示变量的标识符, expression为变量的初始值表达式. 这里定义的变量都可以被子规则表达式直接使用.
另外临时变量可以存在在两种上下文层次中, 分别是inside和outside, 由location属性指定. 见下图:
实际上规则引擎在建立实体节点上下文时, 隐含的建立了两层上下文:
  • 内层是分组后的上下文, 其中有我们熟悉的f, s等数据变量, inside临时变量存放与此;
  • 外层是分组前的上下文, 这个上下文的作用主要就是存放outside临时变量;
同图中也可以看出, 每个分组都有自己独享的inside临时变量, 但共享相同的outside临时变量.
元数据参考后续通过代码产生.
API参考后续通过代码产生.

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|DiscuzX

GMT+8, 2025-7-18 05:14 , Processed in 0.075051 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表