复杂的企业应用程序通常有着不同的业务逻辑。这些业务逻辑中的前置条件和后续系统动作(也就是我们所说的规则)总是变化的。而且,比起技术和编程,我们这里所说的规则更需要特定领域的知识介入。我们在实现这些规则时不应老想着靠代码,反而应该驻留在代码库之外,由具有核心领域专业知识的人去进行规则编写(他们只需要具备极少的技术及编程知识)。有一种特定类型的软件工具,也就是规则引擎可以帮助解决难以确定的业务规则需求。领域专家们并不需要擅长编码和技术,就像企业的品牌和营销团队不需要知道企业门户和移动应用程序的底层技术,但他们需要善于撰写编辑图像、横幅和其他内容等(这些工作用 Instagram 账号就能轻松做到)。Adobe aem 是提供无代码 / 低代码内容创作的内容管理系统之一。新兴技术和云平台不断提出低代码和无代码的解决方案,而且这些解决方案也获得了需求市场广泛的接受。本文介绍了一种将业务操作外部化到低代码工具中实现的轻量级方法,使得具有各自领域专业知识的人员也可以实现业务规则方面帮上忙。
虽然我们已经可以在市场上选用到很多规则引擎,比如 Drools(它是一个功能丰富的业务规则管理系统)、Easy Rules、Rule Book、Oracle Rules SDK、Blaze (fico)、IBM Decision Manager 等等。这些规则引擎通过各自的丰富特性(包括版本控制)以声明的方式启用规则管理,这对许多应用程序来说通常非常有用。然而,在某些不太复杂的解决方案中,它们往往是多余的,并没有得到充分利用。反而显得维护这个额外的组件更像是一种负债而不是一种资产,开销大于实际使用价值。
在本文中,我们试图说明如何利用 Java 的固有特性,用尽可能简单的方式实现外部化规则,而不局限于附加框架的任何传递依赖。当技术规则(用 Java 编写的代码片段)需要外部化并且可能频繁更改时,这种方法非常有用。因此,这种方法在任何 Java 生态系统中都具有同等的价值,无论框架是什么。为从外部源(例如文件或 URL)加载的规则提供一个简单的基于声明式模型的 POJO,这些规则是代表一个谓语或者一个等同于 lambda 表达式的 Java 代码片段。外部源的内容是 Java lambda 风格的表达式或 Java 代码片段,来源范围包括本地数据库及云资源,这样就可以实现在应用程序之外编写规则,甚至不需要应用程序停机。我们可以很容易地将其与 Spring 微服务和云配置进行集成,用不用云总线均可。这种方法提供静止加密以确保业务规则的安全性(机密性和完整性)。另外,除了支持 Jasypt 和 Spring Config Ciphering 之外,任何自定义安全性都可以插入其中。
处理业务逻辑频繁变化的最传统和最理想的方法是规则引擎。规则通常是一组 IF-THEN 条件。
规则≡前置条件 + 后续行动(RULE≡ CONDITION+ACTION)
规则引擎是软件工具,简单来说,为我们提供了在源代码之外设置规则的能力。规则引擎使得半技术人员 / 非编程人员以不同的方式设置规则。利用领域特定知识,这些规则引擎可以提供 GUI 驱动的、直观的规则编写。Drools,DTRules,Oracle Policy Automation system,Easy-Rules 等就是常用的规则引擎。
传统的规则引擎帮助领域专家能够脱离代码库编写规则集和行为,这对于复杂的大型业务环境非常有用。但对于较小的并不复杂的系统来说,考虑运行在本地或云基础设施上的经常性成本以及许可证成本等,结果证实,往往对于这类小系统、简单系统来说,规则引擎功能必要性大不,难以得到充分利用。对于小型团队来说,添加任何需要额外技能集的组件都是对带宽的浪费。一些商业规则引擎有陡峭的学习曲线,一直在追求更好的规则引擎性能,对用户使用的性价比考虑得比较少。在本文中,我们试图说明如何成功地在源代码之外维护规则,以执行在 Java Tech-Stack(像 Spring Boot)上运行的中型系统,使其他用户自定义定制这些规则更容易。
这种方法适用于那些无法负担专用的规则引擎开销及其基础设施、维护、经常性成本等等成本的团队,并且团队中的领域专家具有软件基础,或者团队成员身兼数职。
这种极简主义的方法适合创业的人。深究大公司的起源,我们不难发现,许多大公司一开始也是小团队,预算都必须花在刀刃上,拥有优秀的人才和创新的想法。所以,综合考虑来看,虽然团队的规模可能无法完全确定,但 10 人及 10 人以下的团队在面临同样的限制条件时是可以充分利用这种方法的。
更大规模的团队也能从这种方法中受益。此外,砍掉一些专用的规则引擎也能受益,除非所有酷炫的核心功能(商用规则引擎主打功能)都是必需的。
下图列出的步骤描述了这些组件,也展示了该机制下更高层次的功能概览。
配置存储了规则,这些规则只是简单的 java 方法或布尔表达式。为了避免 Java 语法的冗长,我们也采用了 Lamda 和方法引用,从而在 Java 方法的语法之上提供了一个额外的抽象层。
配置存储了由领域专家编写的规则。以下这些都是有效的语句,用类似 lambda 体的语法编写,以捕获专家的意图。例如,要将娱乐软件评级委员会(ESRB)定级的成人视频游戏划分到合适年龄范围的产品,领域专家可以这样写:
customer -> customer.getAge()>=17
配置可以是一个文件、原始字节流或 URL,可以放置在任何地方。放置位置选择广泛,如下均可选择:
一个本地磁盘上的文件 。
一个数据库(包括 SQL 和 NoSQL)。
一个远程网络位置(如 HTTP URL、原始 TCP 套接字等)。
放置到云上的一个位置(如亚马逊云科技的 S3 bucket 或谷歌的云硬盘等)
框架提供了对源编码、安全性和传输的全面抽象。任何标准或专有的编解码器(CODEC)都可以用于存储和检索规则,这同样适用于两种主要的安全范式 - 机密性和完整性。就这一点而言,每个人都可以编写自己的适配器(Adapter)。只有这样,它的检索过程的最后一步应该产生 Java 表达式或谓语中的规则。
通过另外做一些工作,增加一种机制,这种机制可以使流规则适合采用任何标准协议的浏览器(如 WebSocket、HTTP/2、XMPP、MQTT)。这种方法甚至可以与 Web-assembly 结合在一起,在 Web 浏览器中集成安全和复杂的规则。
虽然传统来说,在开始使用 SDK 之前我们最好先了解一下它的结构,但为了方便和简单,我们颠倒了顺序。在本节中,我们将通过简单的问题陈述或用例来说明规则是如何被外部化的。
用户必须具有使用 Java SE 1.8 或其更高版本进行软件开发的经验。除此之外,还需要一台带有 Java SE 1.8(带有 IDE)的标准机器。在整篇文章中的所有示例中,IDE 将选用 Eclipse,而构建工具选用 MAVEN。
从(https://github.com/trainerpb/easyrule/blob/master/externalize-rules.jar)下载 Library 并将其保存在本地路径上,例如D:\ externalize-rules.jar。这里是完整的源代码(https://github.com/trainerpb/easyrule)。
创建一个 Maven 项目,将编译器源代码层级设置为带有依赖项的 1.8,如图所示。
问题陈述: 一个高档的服装零售店是经常在不同的场合为顾客提供折扣,基于一系列不同的可参考标准,比如职业、收入水平、工作 / 居住场所、教育水平等等。为了不复杂,我们假设一个简单的客户模型类如下:
我们进一步假定一个类 Repo_Customer,它作为客户信息的来源,并根据特定的资格标准挑选符合折扣条件的客户。
然后,我们可以使用以下任何一种或几种的组合来定义折扣资格规则:
值得注意的是,Lambdas 没有那么冗长。主题专家不用深入了解 Java 编程语言,就可以使用它们定义规则。
我们将折扣规则保留在代码库之外,因为它可能经常更改。然后在所有可用存储选项中选择最简单的一个,将规则存储在本地磁盘的一个文件中。SDK 帮助加载和执行这些规则。以下文件存储在本地磁盘上:
我们在文件中创建了自己的标记标准(它可以是任何一个设计和实现,不受任何特定实现的限制):
<RuleName><ColDelimiter><TargetModel><ColDelimter><LamdaExpression><RowDelimiter>
最后,我们写个小代码来检查我们的规则,看看规则是否顺利执行:
现在,我们已经成功地启动并编写了一个简单的快速启动程序,我们可以回过头来开始探索这个 SDK 做了什么,示意图如下。
1. 总体方案结构:
com.yourcompany.libs.externalizedrules.poc.RuleExecutor<T, R>是一个抽象类,它定义了一个规则执行方法。规则的实现需要为这两个抽象方法进行子类化,通过用户应用程序代码(提供实现的)。R 表示决策的结果,T 表示决策所基于的输入对象。例如,如果我们想定义一条规则来过滤 60 岁以上的所有客户,我们需要子类化。RuleExecutor<DTO_Customer, Boolean>
com.yourcompany.libs.externalizedrules.poc.models.RuleExecutionConfig 这个类从配置源加载规则,并且必须事先初始化。这个类负责以下工作:
1. 从配置源加载原始规则库
2. 将规则基础片段合成 java 代码 (例如 Lamda)
3. 编译合成 Java 代码
4. 最后将这个类加载到主内存中,以便执行规则。
我们已经在【2】中使用 PredicateRuleExecutor 。这是一个特殊的实现
com.yourcompany.libs.externalizedrules.poc.RuleExecutor<T, R>,它的值是一个布尔值。
它执行以下 3 个任务:
1. 加载计算值为 true 或 false 的规则。也就是说,它将原始规则库或 lambda 表达式转换为一个 java 方法,该方法接受一个 Object 并返回一个布尔值,即形成一个
java.util.function.Predicate<java.lang.Object>
2. 其框架,特别是PredicateExecutionContextLoader,它是RuleExecutionContextLoader 的子类,为底层任务提供了抽象。
3. 如果需要加载加密的,编码的 / 代码混淆的字节序列,根据一些标准的 / 定制的编解码器,必须重写该方法,
com.yourcompany.libs.externalizedrules.poc.services.PredicateExecutionContextLoader.load(InputStream
4. 这个方法的一个优点是,它以字节为单位工作。因此,规则可以从文件系统、URL、云存储、数据库等地方加载。
5. 从本地文件加载原始规则。这个方法getConfigStream()返回一个 java.io.inputStream。它提供了从任何地方加载原始字节的灵活性。
我们将简短说明如何从几个主要的和常用的源代码加载规则。
在本节中,我们将探讨一些常见的而且重要的可以加载配置的源文件。
HTTP 上的远程 URL
2. 亚马逊云科技 S3 bucket
要从 S3 bucket 中加载规则,我们只需要做以下更改:
粗略来看,SDK 的实用程序是一个 PoC(Patch Output Converter 成批输出转换程序)。然而,创作者们正在努力把它塑造成一个开源项目。在这一节,也就是文章的最后一部分,我们会着重强调它的一些显著特征:
该框架可以轻松地与不同的安全框架(如 JASYPT)协作,以确保业务规则的完整性和保密性,因此既不能滥用明文业务规则,也不能注入自己的规则。
因为它只是普通的 java,所以它非常适合所有流行的框架,例如
Spring and Spring BOOT
JSF
Play
ZK
无论是对于有云总线的,还是没有云总线的 Spring 云配置(Spring Cloud Config),它都非常适合。
协议中立性使得它可以与任何源代码协作使用,无论源代码是部署在云提供商的云服务中,还是部署在本地(on-premise)存储中。
人们可以在任何云提供商或本地(on-premise)存储上实现自己的自定义编解码器。
.我们可以使用 JMX hook 重载已加载的配置,而无需重新启动应用程序。
作者简介:
Soham Sengupta 在学术界、研究和产业界有 16 年的工作经验。作为一名移动计算和网络技术的硕士,他一直在为 PAYBACK 公司(印度)进行数字化转型。他是一个真诚的基础科学崇拜者,狂热的文学爱好者,充满激情的程序员,喜欢在社交平台上分享自己的想法,而且这些想法往往被证明是超前的、具有创新性的。如果您想了解更多关于作者的信息,可以在他的 LinkedIn 上找到。
Srijeeb Roy 在 IT 行业拥有超 23 年的经验。曾获印度 Jadavapur 大学的计算机科学与工程学士学位。他过去曾领导 TCS 垂直保险的 Java 基地团队,目前专注于各种数字技术加速器,这些加速器可以帮助 TCS 客户加速他们的数字旅程。他在 Java/J2EE/JEE、Spring 技术和混合移动应用框架方面拥有超过 20 年的经验。他曾经在 Infoworld.com(前身是 JavaWorld.com)上写过几篇关于 Java SE、EE 和 ME 的文章。
查看英文原文:
https://www.infoq.com/articles/java-external-rules-engine/
点击底部阅读原文访问 InfoQ 官网,获取更多精彩内容!
今日好文推荐
点个在看少个 bug 👇