使用Infinispan创建自己的Drools和jBPM持久性

2022-04-14 00:00:00 创建 自己的 对象 会话 持久性

为什么?


如果您正在阅读本文,那么您可能已经有了一个“为什么”来重新定义流口水使用的持久性方案,但是回顾一些很好的理由来做这样的事情是很好的。 重要的是,您可能会认为出于一个或多个原因,为流口水设计的JPA持久性方案无法满足您的需求。 我发现的一些常见的是:

给定的模型不足以进行我的设计:为持久化流口水组件(会话,流程实例,工作项等)而创建的当前对象当前尽可能小,以使数据库具有佳性能,并且大部分可操作数据存储在映射到Blob对象的字节数组中。 这种方案足以使drools和jBPM运行时正常运行,但对于您的域可能还不够。 您可能希望将运行时信息保存在一种方案中,该方案更易于从外部工具查询,而这样做则需要丰富数据模型,甚至创建自己的模型。

我正在使用的持久性与JPA不兼容:目前有很多持久性实现不再使用我们曾经知道的数据库(分布式缓存,键值存储,NoSQL数据库),并且该模型通常需要额外的映射和特殊功能坚持存放在这样的仓库中。 这样做,有时JPA并不是我们的理想之选

每次加载drools组件时,我都需要从不同的来源加载特殊实体:当我们拥有复杂的对象和/或外部数据库时,有时我们希望新模型以特殊的方式与我们拥有的对象相关联。 也许我们想确保我们的会话以特殊的方式绑定到我们的模型,因为这对我们的业务模型有意义。 为此,我们必须更改模型

怎么样?

为了为我们的会话创建自己的持久性方案,我们需要清楚地了解JPA方案是如何构建的,并将其用作构建自己的持久性方案的模板。 此类图显示了如何实现知识会话的JPA持久性方案:

看起来很复杂,对吧? 不用担心 我们将逐步了解它的工作原理。

首先,您可以看到我们有两个StatefulKnowledgeSession的实现(或者,如果您使用的是Drools 6,则为KieSession )。 一个完成所有“管脚魔术”的任务是StatefulKnoweldgeSessionImpl ,而我们将要使用的任务是CommandBasedStatefulKnowledgeSession 。 它与持久性无关,但是通过将每个方法调用都包含在命令对象中并将其执行导出到命令服务中,对持久性有很大帮助。 因此,例如,如果您对这种类型的会话调用fireAllRules方法,它将创建一个FireAllRulesCommand对象,并将其交给另一个类执行。

这种基于命令的实现使我们能够准确地完成在drools环境中实现持久性所需的工作:它使我们能够在对会话的每次方法调用之前和之后实现操作。 那就是SingleSessionCommandService的地方
该类很方便:此命令服务包含一个StatefulKnowledgeSessionImpl和一个PersistenceContextManager。 每次必须执行命令时,此类都会创建或加载SessionInfo对象,并告诉持久化上下文将其与StatefulKnowledgeSessionImpl的所有状态一起保存。

那是复杂的部分:实现会话持久性的部分。 几乎所有其他内容的持久性都可以通过一组给定的接口轻松完成,这些接口提供了一些方法来实现如何加载与会话相关的所有其他内容(流程实例,工作项和信号)。 只要创建一个合适的经理及其工厂,就可以委托他们将任何东西存储到任何地方(或者做任何您想做的事情)。

因此,看完所有组件之后,现在是开始思考如何创建自己的实现的好时机。 在此示例中,我们创建了一个基于Infinispan的持久性方案,并将向您展示实现该方案的所有步骤。

步骤1 :(重新)定义模型

在大多数情况下,当我们想以自己的方式持久流口水时,我们可能会想尽办法做到。 即使我们不希望更改模型,也可能需要向模型添加特殊注释才能与您的存储框架一起使用。 另一个原因可能是您想以一种特殊的方式存储所有事实,以便与其他旧系统进行交叉查询。 只要您了解所创建的模型,那么每次您在知识会话上调用方法时,持久性方案都会对它进行序列化和反序列化,就可以按照您希望的方式进行字面上的重新定义。因此,请始终尝试使其保持简单。

这是我们为这种情况创建的模型:

没什么花哨的,只是流口水相关的所有事物的扁平化模型。 我们对这种模型不太有想像力,因为我们只是想向您显示可以更改它。

在该模型中要注意的一件事是,我们仍然保存这些对象的所有内部数据的方式与为JPA持久性存储数据的方式几乎相同。 的区别是JPA将其存储在Blob中,而我们将其存储在Base64加密的字符串中。 如果要更改字节数组的生成和读取方式,则必须创建自己的以下接口实现:

  • org.kie.api.marshalling.Marshaller进行知识讲座
  • 流程实例的org.jbpm.marshalling.impl.ProcessInstanceMarshaller

但是提供一个示例可能会花费大量时间,甚至可能需要整本书来解释,因此我们将跳过。

步骤2:实现PersistenceContext

在某些情况下,重新定义PersistenceContext和PersistenceContextManager就足以实现您的所有持久性要求。 PersistenceContext是一个对象,负责实现工作项和会话对象的持久化,方法是实现持久化工作项和会话对象的方法,并通过ID查询它们并将它们从特定的存储实现中删除。 PersistenceContextManager负责为所有应用程序创建一次或在每个命令的基础上创建PersistenceContext。 comand服务将在需要时使用它来持久化会话及其对象。

在我们的案例中,我们使用Infinispan缓存作为存储实现了PersistenceContext和PersistenceContextManager。 不同的PersistenceContextManager实例将可以通过Environment变量访问所有配置对象。 我们已经使用Environment中已定义的键来存储Infinispan相关的对象:

  • EnvironmentName.ENTITY_MANAGER_FACTORY用于存储基于Infinispan的CacheManager
  • EnvironmentName.APP_SCOPED_ENTITY_MANAGER和EnvironmentName.CMD_SCOPED_ENTITY_MANAGER将指向Infinispan缓存对象。

您可以在这里看到该代码:

在这一点上,我们有一些非常重要的步骤来重新定义我们的流口水持久性。 现在,我们需要知道如何配置我们的知识会议以使用此组件。

步骤3:为我们的工作项目,流程实例和信号创建经理

现在我们有了持久性上下文,我们需要教会会议如何正确使用它们。 知识会话具有一些可以配置的管理器,这些管理器使您可以修改或更改默认行为。 这些经理是:

  • org.kie.api.runtime.process.WorkItemManager :它管理工作项目的执行时间,将其与适当的处理程序连接,并在工作项目完成时通知流程实例。
  • org.jbpm.process.instance.event.SignalManager :它管理何时向进程发送信号或从进程发送信号。 由于流程实例可能被钝化,因此需要
  • org.jbpm.process.instance.ProcessInstanceManager :它管理在创建,启动,修改或完成流程实例时要采取的动作。

这些接口的JPA实现已经可以与持久性上下文管理器一起使用,因此大多数时候您不需要扩展它们。 但是,与Infinispan相比,我们必须确保流程实例的持久性要比JPA多,因此我们必须以不同的方式实现它们。

一旦有了这些实例,就需要为每种类型的管理器创建一个工厂。接口名称相同,但后缀“ Factory”除外。 每个用户都接收一个知识会话作为参数,从中可以获取环境对象和所有其他配置。

步骤4:配置知识会话

现在我们已经创建了不同的经理,我们将需要告诉我们的知识会议以使用它们。 为此,您需要使用SingleSessionCommandService实例创建一个CommandBasedStatefulKnowledgeSession实例。 顾名思义,SingleSessionCommandService是一个用于一次针对一个会话执行命令的类。 SingleSessionCommandService的构造函数接收创建适当会话并对其执行持久化方式所需的所有参数。 这些参数是:

  • KieBase :具有用于会话运行时的知识定义的知识库。
  • KieSessionConfiguration :我们在其中配置管理器工厂以创建和处理工作项,流程实例和信号。
  • 环境 :用于其他目的的一袋变量,我们将在其中配置持久性上下文管理器对象。
  • sessionId(可选) :如果存在,则此参数在存储中查找已存在的会话。 否则,它将创建一个新的。

同样,在我们的示例中,我们使用的是Infinispan,它不是基于引用的存储,而是基于值的存储。 这意味着一旦您对infinispan说要存储一个值,它将存储它的一个副本而不是实际对象。 流口水持久性中的某些内容通过基于引用的存储进行管理,这意味着您可以告诉框架持久化对象,更改其属性,并在提交事务后查看存储在数据库中的那些更改。 使用infinispan不会发生这种情况,因此您必须在命令执行完成后实现对缓存值的更新。 对我们来说幸运的是,SingleSessionCommandService允许我们通过实现拦截器来做到这一点。

拦截器基本上是您自己的命令服务,用于包装默认命令。 您可以告诉每个命令在每次执行之前或之后添加更多行为。 这里有一些图表来解释它是如何工作的:

如您所见,SingleSessionCommandService允许命令服务实例实际调用命令的execute方法。 并且由于命令服务的拦截器扩展,我们可以在链中添加任意数量的内容,从而使我们可以在每次需要执行命令时执行下一个序列图之类的内容:

在我们的例子中,我们创建了几个拦截器,并将它们添加到SingleSessionCommandService中。 确保在完成命令后存储对会话对象所做的所有更改。 另一个允许我们对流程实例对象执行相同的操作。

总的来说,这是我们现在需要创建知识会话以实际使用infinispan作为持久性方案的方式:

复杂吧? 不用担心 还有另外两类可以简化配置。

步骤4:创建我们自己的启动服务

是的,每次我们想要创建自己的自定义持久性知识会话时,我们都可以编写大量代码。 这是一个自由的世界(大部分情况下)。 但是,您也可以将此实现包装在带有两个公开方法的单个类中:

  • 一个创建新的会话
  • 一个加载先前存在的会话

并在内部创建所有配置,并在需要更改一项或多项更改时将其合并。 Drools提供了一个接口来充当此协议的接口,称为org.kie.api.persistence.jpa.KieStoreServices

我们创建了此接口的自己实现,并且还创建了一个静态类(称为InfinispanKnowledgeService)来访问该接口。 这使我们能够创建如下会话:

结论

流口水的持久性似乎很难理解和工作,更不用说以自己的方式实现它了。 但是,我希望这对那些需要以特殊方式实现流口水持久性,或者甚至想知道是否可以通过JPA以外的其他方式实现流口水持久性的人有点神秘。

另外,如果您希望看到为使其工作而进行的修改,请参见以下三个请求请求:

  • https://github.com/droolsjbpm/droolsjbpm-build-bootstrap/pull/38
  • https://github.com/droolsjbpm/drools/pull/198
  • https://github.com/droolsjbpm/jbpm/pull/166

在此JIRA票证中指定了向Drools添加此功能的功能请求。 如果您希望将其作为核心drools项目的一部分,可以随时对其进行投票!

相关文章