Drools——为什么使用规则

目录

为什么使用规则

规则独立

规则执行链

规则的原子性

规则顺序

规则执行生命周期

规则之间的协作

使用BRMS让更多人参与到规则开发

让规则引擎做这些事情

规则引擎算法


为什么使用规则

关于为什么规则很有用,在这一点上,你大概一直都有点困惑。如果我们这样想,就一条或几条规则而言,我们认为可能直接使用类似于Java的命令式代码会更好一些。作为开发者而言,我们通常将需求拆分成我们将要遵循的步骤列表,并且避免所有事情被集中掌控。业务规则背后主要的优势无法在一个或几个规则中体现,而是体现在由大量的、随时变化的规则组定义的一个非常复杂的系统,如果要使用常规代码实现它将会需要很大的工作量去维护它。

随着业务规则代码的增长,很多规则共同工作来定义一个复杂的系统。无论我们需要实现新得需求、更新现有的需求、替换参数、或者使用意想不到的方法改变系统行为的结构,我们唯一要对规则做的事情是实现现在需要的规则,移除现在不需要的规则。业务规则大概就是基于以下原则:

  • 相互独立
  • 易于更新
  • 每条规则控制所需要尽量小的范围(单一性)
  • 允许不同背景的人相互协作

规则独立

一个业务规则不需要做太多事情。一个大的基于规则的业务系统就是它是由大量的规则相互交互组成。然而,大多数时间,规则都不需要直接知道这种交互。这就是我们说的规则之间的相互独立的意思。每条规则应该能监测到特定的一组情况并且能做出行动,而不需要其领域之外的其他任何数据。

当我们思考规则时,我们通常有很多方式可以形象化的描述它。拿起一本法律书籍,你会发它们是由一组规则组成的,每一组都是以条款形式存在。大多数都是提出当前的场景以及在这个场景下相应的行为和解释。这些大多数条款都不会提及到其他的条款。这么做的原因是让规则更易于阅读和理解和更不容易被误解。

当你为组织定义规则时同样适用于这些原则。每条规则应该尝试不依赖其他规则。反而,规则应该只依赖于领域所提供的数据。这会让规则具备自解释特性,而不必再除了业务规则的内容之外,附加其他的解释。

然后有时候规则确实会以间接的方式依赖其他规则。假设我们制定的规则会在另一个规则的条件下被执行。这种假设下创建的数据我们称之为推论,它们对于扩展规则的可用性非常有用。

规则执行链

正如上个章节提到的那样,一个好的业务规则是一个相互独立的实体,除了领域数据不依赖任意其他东西来决定其意义。这并不是说每个规则都工作在一个非常复杂的结构体上,否则你会得到一个难以维护的非常复杂的规则。

如果一个规则很复杂,你需要将其拆分为多个更小的规则。然而,尽管这么说,规则的独立性还是非常重要的,你不应该在规则中明确的调用其他规则。这意味着使用顺序流来进行控制,而我们已经表述过,声明式编程不允许这种情况。

相反,我们可以通过对基础领域和向基础领域添加的信息做出假设 来进行分割复杂度。这些假设称之为推理。作为条件的一部分,其他规则就会用到这个新信息,不管其如果决定。稍后我们看下面的例子来理解如何完成对规则的拆分:

  • 当我们听到火警铃响的时候,我们就推测发生了火情
  • 当发生了火情,我们需要打给消防队
  • 当消防队到了之后,我们让消防队员救火

这三个规则可以组合成一个复杂的规则:当我们听到火警铃声响的时候,我们打电话给消防队,并让他们救火。然而通过将其拆分为了三个简单的组件,我们可以更简单的扩展规则引擎的可用性。我们可以重用我们制定的第一个推理:现在有火情。然后去触发其他的行为,比如说启动应急喷头、关闭电源或者打电话给保险公司。

当一个规则失去意义的时候,我们可以从规则引擎中移除它。当我们需要一个新规则的时候,我们可以创建它,并充分利用现有有用的推理数据。由于顺序流由规则引擎进行控制,我们不需要担心规则代码执行的顺序或者新创建的规则 适合 放在当前其余现有的规则之间的哪里。

规则的原子性

利用现有的推理引擎,随着我们创建更多的规则,规则越简单,它们就越容易扩展。因此让规则尽可能简单是创建一个好的业务规则的原则之一 ,简单到不能再划分成更小的规则,这也是一个规则应该考虑的。这个原则我们称之为原子性。

具备原子性的规则是简单易懂的,它们通常设计为由少量的条件来决定行为,或者是推断某种情况发生。因为它们是相互独立的,所以它们具备自描述性。规则原子性、独立性和推断能力一起使用业务规则可以成为我们定义系统的任何行为的最简单的组件。

简洁性允许我们的系统可以明确的知道为什么要做这个决策,让规则具备自描述性并允许我们可以追踪在干预决策时的每个规则。这也是为什么几千年来法律一直是社会内部规则的基石的原因。

规则顺序

我已经提到过,规则不会遵循特定的顺序。顺序流取决于规则引擎。规则引擎会基于领域中的有用的数据进行决策规则以什么样的顺序执行。这也意味着规则定义的顺序是不重要的,只需要与规则的条件能匹配的数据。

当相同的条件出现在领域中,有一些方法对多个竞争执行的规则进行排序。此排序作为规则的第二优先级,而领域模型中的数据作为决定规则被激活的首要条件。这些排序机制我们会在后面的章节结合一些特殊的情况进行讨论。我们也会定义跟通常情况不一样的规则。当我们编写规则的时候,如果你发现我们控制着所有单个规则的执行顺序,应该重新考虑下正在编写的规则的定义方式。

在声明式编程时,开发人员很难仅仅是看一眼就明白了。尽管如此,在基于顺序无关紧要的基础上,它提供了很多增加开发和运行时的效率的改进,让我们可以在任何地方添加规则。这样做的好处:

  • 规则之间的协作更易于管理
  • 冲突更容易避免
  • 更多的人可以参与到规则的开发,让规则开发可以包容各方面的人成为现实

规则执行生命周期

规则引擎优化的了条件的评估,并确保我们尽可能快的方式决定要触发的规则。然后规则引擎无法立刻执行我们的规则,除非我们指定这么做。当我们找到规则评估对于一组数据为真的时候,规则和触发的数据会被添加到一个集合中。规则的生命周期由两部分组成,并被明确划分为规则评估和规则执行部分。规则评估会把规则行为和触发它们的数据添加到一个称之为Agenda的组建组建中。当规则执行命令发出后,我们会唤醒规则引擎触发所在在Agenda中的规则。

正如先前说的那样,我们不会控制规则什么时候被被触发。至于什么时候触发,这是规则引擎的责任,规则引擎由我们创建的业务规则和提供给引擎的数据。然后一旦规则引擎决定规则要被触发,我们便可以掌控规则触发的时间。这是通过调用规则引擎的方法来完成的。

一旦规则被触发,Agenda中所有匹配的规则都会被触发。规则引擎会更新领域中的数据,如果这些更新产生的新数据会匹配新的规则,新匹配的规则会添加到Agenda中,或者如果这些更新导致一些匹配的规则不再为真,它们将会被取消。这个完整的周期会一直持续到 对于规则引擎的有用的数据来说,Agenda中没有可用的规则或规则引擎的引擎被迫停止。下面的图展示了这个工作流是如何执行的:

Drools——为什么使用规则_第1张图片

这个执行生命周期会一直执行完所有基于我们定义的规则和提供的领域数据规则引擎决定添加到Agenda中的规则。有些规则可能不会执行,有些规则可能会被执行多次。在接下来的章节中,我们将会学习如何控制规则的执行。我们要一直保持已经建立了的业务规则的编写原则:独立性和原子性。我们学到更多关于规则引擎的配置,就越相信它可以做这件事情。那一刻,将会是信心的飞跃,但是,每一步我们都会学到如何去控制规则引擎,直到可以100%的确认它可以精确的做到我们期待的那样。

规则之间的协作

随着规则的创建,顺序流逐渐超出我们的直接控制,这时最大的优势就是我们不必担心代码写在哪里。所有的规则都是独立的,规则执行的顺序流由规则引擎在运行时决定,不论规则代码到底写在哪里。

使用常见的命令式编程语言,比如Java,在程序执行阶段每个指令都会在特定的时间发生,我们需要在代码中找到指定的点,然后添加我们的更新,还涉及到review整套代码。整个设计模式都在围绕着管理这个局限性,提供可以让工作在同一系统的开发者可以相互协作的途径。每个主要的设计模式都可以将代码组分割为modules、methods、classes, 以便可以轻松的管理开发者之间的协作。

然而命令式编程最主要的限制是一旦系统设计完毕,我们不能轻松突破我之前分割的代码的限制。当我们创建设计的时候,必须要预测未来会发生的变化——这些变化会很难被实现。如果我们不能做到这样,多个开发者可能会因为不同的需求修改相同的代码片段,这些代码很容易发生冲突。

这种限制在响应式编程中可以避免,因为它并不关心规则的顺序。在相同的领域模块中,不同的人可以在没有冲突的情况下定义不同切面进行协作,因为新添加的规则放到已存在的规则的哪个位置都可以。不论执行的顺序,执行的结果都相对一样。

让我们看一下下面命令式编程和响应式编程对比的伪代码。当我们向命令式代码中添加更新时,我们不能在任何位置做这件事情。如果你把它放在不同的地方,特定的地方会得到特定的更新语义,它既不能像期待的那样工作或者不能执行,你会在下面看到对比:

Drools——为什么使用规则_第2张图片

另一方面,每个业务规则都被定义为一个孤立的代码块。规则编写人员可以在没有任何问题的情况下,在任何地方做添加工作。这使得在协作环境下的应用开发更容易,因为它更不容易出现冲突问题,如下图所示:

Drools——为什么使用规则_第3张图片

我们更少的机会冲突,那么我们就可以集中时间和精力在如何成功构建系统上,而不是担心不同的组件和组件内部进行合并的解决方案。在没有冲突的情况下,有更多可能的点去添加代码,这也让更多的人参与到开发周期中成为可能。这将会大幅度加快开发和软件更新速度。

使用BRMS让更多人参与到规则开发

由于业务规则在程序开发期间提供的更高的可协作性,我们可以在系统中增加很多人去定义规则决策。当前紧跟着我要面对的瓶颈是找到更多知道如何编写规则的的人。编写规则是一件技术活,至少在刚开始是这样。这需要一定水平的知识在如何去定义条件和行为上——我们将在下一个章节进行覆盖的主题,可以让更多的人仅用一点时间就可以学会如何编写规则。

即使我们能让技术人员快速的学会如何编写规则,那也是不够的。这并不是由于技术限制的原因,而是由于掌握了可写成业务规则的实用知识的人并不一定技术娴熟或最可用。当然,也可能是这样,你大概已经找到一组最好的可以工作在基于业务规则的系统中的成员。然而,在大多数情况下,他们有实用的知识,但没有时间去设计和学习如何写技术规则。

对于这些组中的业务专家来说,这是一个可以让他们以友好的方式来编写规则的平台。这些由一些比较友好的编辑器的组合而成,并且具有版本控制和发布功能的平台,我们称之为业务规则管理系统(BRMS).基本上,业务规则专家会使用它们熟悉的日常语言去创建规则,并用其进行定义决策。你将会在第五章学习到更多关于友好的编写规则的方法。现在提一下,我们可以使用编辑器用自然语言进行定义规则,这允许业务专家可以像技术专家一样的速度来定义业务规则。下面的截图我们可以看到drools的业务规则管理系统KIE Workbench里的一个编辑器。

Drools——为什么使用规则_第4张图片

让规则引擎做这些事情

至今为止,我们涵盖介绍了业务规则的结构。每当解释规则是如何执行的时候,我们总是说规则引擎会做好它。当我们使用规则引擎的时候,我们相信规则引擎会基于我们提供的领域数据来决定规则的触发。当前阶段,我们会尝试去定义规则引擎是如何定义规则何时执行。前面的章节中,我们简单的展示了业务规则是如何转换为决策树的,它遵循响应式编程的规范,基于数据进行决策。在这个章节,我们会尝试去解释这个结构是如何基于我们定义的规则帮助我们生成更高效的决策树。

规则引擎算法

规则引擎使用特定的算法将我们定义的规则转化为可执行的决策树。决策树的性能取决于生成算法的优化方式。drools6框架专注于更高的性能而定义了属于自己的算法。这个算法的名字是PHREAK,它的创始人是Mark Proctor。它是对现有由Charles Forgy发明的算法RETE的一系列的优化和重新设计。PHEARK是到现在为止开源代码中最高效和可行的算法。

在生成的决策树中,在我们规则中的每个条件都会转化为树中的一个节点,不同的条件如何与其他条件联系的取决于这些节点的连接。随着我们往规则引擎添加数据,规则将会被批量评估,使用最优路径流经网络。当数据到达叶子节点的时候,决策树执行完毕,叶子节点表示要响应的规则。这些规则会添加到一个集合中,当执行一个命令就会触发集合中所有的规则或规则组。

由于对规则中条件的连续实时评估,规则引擎为了效率将在内存中为所有数据提供规则的评估。我们将在后面的章节讲述算法是如何创建决策树的。

每当我们添加很多数据到规则引擎中的时候,它们会从根节点进入决策树。决策树的每个优化都是根据以下几点来工作的:

  • 尝试将所有的条件打破为更小数量的单元,为了可以在决策树中尽可能的重用。
  • 尝试在去到下层节点的时候仅需要一个操作,直到条件的评估得到false或到达叶子节点,并没标记为可执行的规则

每一片数据在评估时都使用尽可能高效的方式。对这些评估的优化是规则引擎的主要关注点。在接下来的章节,我们将会讨论为了尽量快的创建我们自己的规则,如何充分利用这些优势制作规则。

 

你可能感兴趣的:(规则引擎)