源宝导读:随着企业数字系统应用的越来越深入,业务计算方式也变的越来越复杂,灵活度要求也越来越高。本文将介绍通过将配置动态转换成可执行代码的方式,解决业务计算高度灵活化配置的技术方案。

一、背景

ERP本质上是一种“业务密集型”软件系统,将各种数据经过复杂的业务计算后,以更具业务价值的形式显示给客户。在某些特殊的复杂业务场景下,客户对业务计算逻辑的灵活性有很高要求,这就需要将计算规则做成可配置化,比较通用的做法是,将计算逻辑中的变量抽取成配置项,但不管如何抽取,计算逻辑的代码都会有”写死“的部分,如果客户对计算逻辑的灵活度要求更高时,这种做法将无法满足需求。比如在ERP中有一个业务功能,需要对”动态成本“数据进行计算,但业务对计算逻辑的灵活度要求非常高,对每一种数据对象的计算规则都可能不一样,每一个计算表达式都可能会自定义,如果用传统的可配置化方案,将难以实现这种高度灵活的配置,即使实现出来,也会让系统和配置非常复杂,造成系统不稳定,用户难以使用。我们需要一种更优雅的解决方案。

我们发现,可以将规则配置动态转换成可执行代码,通过这种方式既满足高度灵活的规则配置,又可以大大简化系统实现,大幅提升系统的稳定性。本文将从”技术原理“和”架构实现“两个层面,详细介绍我们的解决方案。

二、技术原理

规则的可配置化的重点在于规则字符串的动态转换为可执行代码的环节,最终实现的效果是能够根据规则字符串的定义,从内存实体对象中获取指定的字段值并完成计算,再赋值给目标实体对象的指定字段中。

首先我们看下如何根据定义的字段名称从内存实体对象中取值。在.Net平台下,最常用的动态取值方式是反射和方法委托,众所周知,反射性能是很低下的,因此在数据转换这样的大数据量高实时性的场景下,是无法接受的,而委托呢,其实性能和原生取值相差不多而且实现也很简单,是一个比较好的取值方式,其性能优于反射取值约30-50倍,100W次循环取值仅需耗时数毫秒。取值方式如下:

那么是否委托取值就能解决规则取值的问题呢?显然还不行,从代码中我们发现每个字段取值都需要创建一个委托对象,这些委托对象的缓存、提取都需要通过列表对象进行处理,于是代码可能变成如下:

这个代码是极度高效的,但是问题来了,我们期望返回值是通过规则配置的,这样硬编码肯定不行,我们希望由配置来决定返回值而不用修改代码。有办法吗?当然有。

我们先了解一下.Net运行机制,.NET运行时任何有意义的操作都是在堆栈上完成的,而不是直接操作寄存器,而这个堆栈操作则是由中间语言MSIL来执行的,C#、F#等语言在执行前都会编译为IL语言来执行。而我们前面所写的这段C#代码实际上也会编译成IL语言,所以,要实现不改代码而又能动态定义返回值,那么我们可以通过Emit构建IL指令,动态的生成该方法。

这段代码用Emit怎么构建IL指令呢?

首先在当前程序域下创建一个新的程序集,并定义一个动态类Builder对象:

然后再为这个动态类构建一个类初始化方法:

至此,一个OrderClass动态类已经构建完成,下一步是构建GetValue方法:

构建完成后,这个方法仍然是一个空方法,并没有实现返回值return order.Money。如果要在定义好的方法内实现我们需要的功能,则需要掌握MSIL语法指令,因为篇幅原因,这里不对IL指令展开说明,有兴趣的可以查阅相关资料仔细了解。这里我们可以用快捷方法来构建IL指令,就是通过工具直接将C#代码翻译为IL指令,然后在代码中实现。Visual Code的IL View插件提供了直接查看当前C#代码IL指令的功能,另外也可以将C#代码编译为DLL后,通过反编译软件查看IL指令。比如以上OrderClass我们可以通过反编译软件dnSpy来查看GetValue实现返回值的IL指令:

有了以上指令,那么我们就能在定义的方法中调用IL指令来实现我们所需的功能,比如我们在GetValue方法中返回Money字段就可以这样实现:

至此,我们已经完成了这个类的构建,最后我们将这个构造类动态创建为Type类型,以供后面程序使用:

我们已经有了高性能且能动态构建的取值(赋值原理相同)方式,但是离使用规则实现对象间的数据流转还有一定的距离,因为数据流转除了取值赋值之外,还有规则运算。一段字符串形式的规则如何能在.Net程序中高效计算并得到结构呢?如果用IL直接构造,显然过于复杂,但是如果你用过强大Lambda表达式,那么你会发现其正好能支撑我们的规则运算且足够方便。.Net对Lambda表达式提供了强大的支持,能够将对象取值运算操作通过Lambda表达式实现并通过Fun委托输出计算结果,比如我们规则运算就可以用Lambda来实现:

.Net还并且支持通过Lambda表达式动态构造一个方法:

是不是很方便?和前面IL指令结合在一起,我们就能实现取值、运算、赋值的可配置了。

当我们在实际应用中去实现数据流转可配置化时,很快就暴露一个新的问题,规则是通过C#的Lambda表达式实现的,也就是说,这个规则必须在程序中硬写代码,无法动态修改规则和存储规则,更无法通过配置文件来实现。所以我们必须实现将字符串形式的规则转化为.Net的Lambda表达式对象。

将字符串转化为Lambda表达式,需要对Lambda表达式的语法结构有一定的了解。Lambda表达式由表达式树构成,其结构似二叉树结构,分左右两个节点,然后由运算符连接,每个节点的子节点结构也是类似,直到末级节点,每个节点都是一个Expression对象的某个类型的继承对象,如下图:

因此,我们需要将文本的表达式先解析为树形结构,然后从末级开始,将节点转化为Expression对象,然后一级级通过运算Expression对象连接起来,最后得到完整的Lambda表达式。

例如一段文本表达式是“Money + TaxMoney*100/(200+1)+100”,我们需要将其解释为(Money + ((TaxMoney * 100) / (200 + 1))) + 100,其树形结构表示为:

那么通过文本解析出来了以上树形结构,如何生成Lambda表达式呢?以上的表达式较为复杂,我们举个稍微简单的表达式来尝试转换,比如从Order对象中取值完成计算Money*100+TaxMoney*50的运算。首先我们将该表达式解析为树形结构,输出为(Money*100) +(TaxMoney*50),这是一个典型的二叉树结构,末级别分别是Money*100和TaxMonet*50,父级为A+B的计算结果。所以我们可以编写如下代码来动态构建Lambda表达式:

我们很容易就完成了将文本表达式动态构建为Lambda表达式来完成计算。在实际的应用中,表示式往往是很复杂的,但是分解到末级,原理都是一样的,如果能熟练掌握构建方法,复杂的表达式依然可以轻松构建。

至此,我们实现字符串规则转换为系统规则的所有技术已经有了,接下来的就是设计一个好的架构,高效、便捷、可扩展的完成规则的可配置化。

三、架构设计与实现

设计架构之前,我们必须先定义规则的格式,这个规则格式应该能够满足我们对业务的有效描述,比如,我们需要将符合条件的A单据的某些字段根据公式计算后写入B表的某个汇总字段,因此,我们可以这样描述“当A满足条件N时,将通过取值公式M得到的值写入B的L字段”,转换成规则就是“if(N(A)==true){ B.L=M(A)}”,在成本业务里,会更复杂一些,会有审批状态P,比如当提交审批(P=P1)时会累加M(A),审批通过(P=P2)时会扣减M(A),于是我们需要将上述规则转化为“if(N(A)==true){ if(P1) {B.L=M(A)}; if(P2) {B.L=-M(A)};}”。这样的表诉看起来有些繁琐,于是我们可以设计一种语法糖来表述这个规则,在.Net下,我们可以用含有Lambda 的格式来表述

“ToSummay<B>(b=>b.L).From<A>(a=>M(a)).Where(a=>N(a)).OnIcrease(P1). OnDecrease(P2)”,如果不用Lambda我们也可用结构化的数据来表述规则,当然也可以根据需求用其它形式来表述。(注:为何要设立OnIcrease、OnDecrease而不只用Where去处理,是因为只用Where的话,不同的审核状态要写多个规则,其实值都是一样,会导致规则量膨胀)

有了规则模板,我们就可以开始设计计算架构了,先分析上面我们所设计的这条规则“ToSummay<B>(b=>b.L).From<A>(a=>M(a)).Where(a=>N(a)).OnIcrease(P1). OnDecrease(P2)”,可以获得几个必要单元:

如果再增加一条规则呢?比如“ToSummay<B>(b=>b.L).From<K>(k=>M(k)).Where(k=>N(k)).OnIcrease(P1). OnDecrease(P2)”,我们可以获得以下几个单元:

整理一下就变成了这样:

看到上图,这正是我们要设计的规则解析计算架构中的计算单元对象。我们继续抽象出接口:

此时,我们的规则解析计算单元的设计已见雏形,我们可以很容易的将前面的规则表达式转换成上述接口的实现(字段赋值的方法需要借助IL实现)。但是我们要把它应用到系统中仍然有难度,因为IDocuenmtExp接口提供的都是Lambda表达式,当一个对象传后,无法直接计算的,必须转变为可执行方法,所以我们需要对上述设计稍作修改:

这样,我们就将文本规则动态转换为了可执行对象,当我们传入一个单据对象实体后,计算对象内部就可以完成验证、取值、计算和赋值操作,这就实现了两个对象间按照配置的规则完成了数据流转,而无需编码。

下图是我们成本计算引擎的整体架构图,可以看到,除了上面介绍的计算核心部件,整个成本计算引擎还包含很多内容,比如:数据合并、数据巡检、预警强控等一系列功能,保障成本业务数据的高质量。

四、总结

本文介绍了ERP成本系统的计算引擎,如何在保障数据质量的情况下,为客户提供高度灵活的可配置计算规则的技术实践。这套高度灵活的计算引擎架构,不仅可以应用在成本数据计算的场景,对所有需要高度灵活配置的计算场景都适用。欢迎对计算引擎感兴趣的小伙伴联系我们成本产品团队,一起交流技术方案和实践心得。

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

作者简介

胡同学: 架构师,目前负责ERP成本应用系统的架构设计与开发工作。

也许您还想看

通过在线编码提高前端代码质量的探索与实践

基于工作单元的高性能实体服务

成本计算引擎动态规则解析技术详解相关推荐

  1. 浅谈Android项目----JSON解析(4种解析技术详解)

    json简介 1.概念:json全称是javaScript object Notation,是一种并轻量级的数据交换格式. 2.特点: 1.本质就是具有特定格式的字符串 2.json完全独立于编程语言 ...

  2. mysql 动态sql 解析_MyBatis详解5.动态SQL

    字节跳动飞书内推! 北京.杭州.武汉.广州.深圳.上海,六大城市等你来投. 感兴趣的朋友可以私我咨询&内推,也可以通过链接直接投递! 海量HC,极速响应,快来和我成为同事吧. 今日头条.抖音. ...

  3. java技术详解_Java反射技术详解及实例解析

    前言 相信很多人都知道反射可以说是Java中最强大的技术了,它可以做的事情太多太多,很多优秀的开源框架都是通过反射完成的,比如最初的很多注解框架,后来因为java反射影响性能,所以被运行时注解APT替 ...

  4. iOS 7: iPhone/iPad应用开发技术详解

    iOS 7: iPhone/iPad应用开发技术详解 作者:刘一道 出版社:机械工业出版社 出版年:2013-11 页数:507 定价:79.00元 ISBN:9787111440512 样章下载:h ...

  5. 视频直播技术详解(7)现代播放器原理

    <视频直播技术详解>系列之七:现代播放器原理 牛小七2016年9月29日发布在 视频直播技术详解 from: http://blog.qiniu.com/archives/7040 七牛云 ...

  6. 技术详解:基于人脸识别的 AI 弹幕

    --------点击屏幕右侧或者屏幕底部"+订阅",关注我,随时分享机器智能最新行业动态及技术干货---------- 有时候,弹幕比剧情还精彩,那些脑洞大开.观点鲜明的弹幕,可以 ...

  7. 《视频直播技术详解》系列之七:现代播放器原理

    七牛云于 6 月底发布了一个针对视频直播的实时流网络 LiveNet 和完整的直播云解决方案,很多开发者对这个网络和解决方案的细节和使用场景非常感兴趣. 结合七牛实时流网络 LiveNet 和直播云解 ...

  8. P2P技术详解(一):NAT详解——详细原理、P2P简介(转)

    这是一篇介绍NAT技术要点的精华文章,来自华3通信官方资料库,文中对NAT技术原理的介绍很全面也很权威,对网络应用的应用层开发人员而言有很高的参考价值. <P2P技术详解>系列文章 ➊ 本 ...

  9. 015. P2P技术详解(一):NAT详解——详细原理、P2P简介

    http://www.52im.net/thread-50-1-1.html 这是一篇介绍NAT技术要点的精华文章,来自华3通信官方资料库,文中对NAT技术原理的介绍很全面也很权威,对网络应用的应用层 ...

最新文章

  1. 导师:CNN 开山之作 AlexNet 都复现不了,延毕吧!
  2. springboot中接口实例化_疫情爆发在家闲出屁的我,梳理一下SpringBoot知识点
  3. SAP 前端技术的演化史简介
  4. 汇编语言(二十九)之数值的二进制和十进制
  5. 《Effective Java》读书笔记 Item 1:考虑静态工厂方法,而不是构造器
  6. L1-056 猜数字 (20 分)
  7. gohu恒温花洒使用教程_使用家庭助理构建更好的恒温器
  8. Windows RDP远程桌面无密码账户
  9. mysql case 2个返回值_MySQL函数简介 2
  10. 课程设计——企业网络项目搭建(下)
  11. python下实现一致性hash
  12. Linux学习笔记08—如何关闭防火墙
  13. mysql的可视化工具_Mysql可视化工具Navicat的基本使用
  14. oracle返回当前日期函数,oracle 日期时间函数使用总结
  15. [管理]鼎捷软件售后服务体验
  16. android WebView加载网页视频
  17. 【Linux】Linux常识28问
  18. 计算机二级考试word论文,office二级考试之word
  19. 语c和c语言,00后黑话等级测试,你能看懂几句?
  20. Qt opengl 图片实现3D效果

热门文章

  1. Mac上Homebrew的使用 (Homebrew 使 OS X 更完整)
  2. [导入]纹理拼接后的Wrap寻址
  3. java乘以2的位计算符号_java编程之:按位与运算,等运算规则
  4. facebook 文本分类_如何禁用和自定义Facebook的通知,文本和电子邮件
  5. 点击右侧导航栏,实现iframe嵌入子页面中div,滑动到最上面
  6. 机器学习模型开发必读:开源数据库最全盘点
  7. 使用RxJava从多个数据源获取数据
  8. openresty 前端开发进阶一之http后端
  9. [LeetCode]--20. Valid Parentheses
  10. crond定时任务详细分析