在过去的几年中,我们经常听到(来自社区和我们的客户)关于如何将Activiti的持久性逻辑从关系数据库交换到其他内容的请求。 当我们宣布Activiti 6时, 我们做出的承诺之一就是我们将实现这一目标。
深入研究Activiti引擎代码的人会知道,这是认真的重构,因为持久性代码与常规逻辑紧密结合。 基本上,在Activiti v5中,有:
- 实体类 :这些实体类包含数据库中的数据。 通常,一个数据库行是一个实体实例
- EntityManager :这些类将与实体相关的操作分组(查找,删除等)
- DbSqlSession :使用MyBatis的低级操作(CRUD)。 还包含命令持续时间缓存,并管理数据到数据库的刷新。
版本5中的问题如下:
- 没有接口。 一切都是一类,因此替换逻辑变得非常困难。
- 在整个代码库中到处都使用了低级DbSqlSession。
- 很多关于实体的逻辑载的实体类中 。 例如,查看TaskEntity complete方法。 您不必是Activiti专家即可了解这不是一个不错的设计:
- 触发一个事件
现在不要误会我的意思。 v5代码将我们带到了很远的地方,并为世界各地许多出色的功能提供了支持。 但是,当涉及到交换持久层时……这并不值得骄傲。
并且可以肯定,我们可以闯入版本5代码(例如,通过用响应于那里使用的方法/查询名称的自定义东西换出DbSqlSession),但这仍然不是很好的设计方法和非常像关系数据库。 而且这不一定与您可能使用的数据存储技术匹配。
不, 对于版本6,我们希望正确执行 。 而且,天哪……我们知道这将需要大量工作……但是它的工作量超出了我们的想象(只需查看过去几周内v6分支上的提交 )。 但是我们做到了……最终的结果很美 (我有偏见,是的)。 因此,让我们看一下v6中的新架构(请原谅我的powerpoint图片。我是编码人员,而不是设计师!):
因此,在v5中没有接口的地方,在v6中到处都有接口。 上面的结构适用于引擎中的所有Entity类型 (当前为25个左右)。 因此,例如对于TaskEntity ,有一个TaskEntityImpl , TaskEntityManager , TaskEntityManagerImpl , TaskDataManager和TaskDataManagerImpl类(是的,我知道,他们仍然需要javadoc)。 这适用于所有实体。
让我解释一下上图:
- EntityManager :这是与数据有关的所有其他代码都可以与之通信的接口。 当涉及特定实体类型的数据时,它是唯一的入口点。
- EntityManagerImpl : EntityManager类的实现。这些操作通常是高层的,并且同时执行多项操作。 例如,执行删除可能还会删除任务,作业,identityLinks等并触发相关事件。 每个EntityManager实现都有一个DataManager。 每当需要持久性存储中的数据时,它就使用此DataManager实例获取或写入相关数据。
- DataManager:此接口包含“低级”操作。 通常包含针对其管理的实体类型的CRUD方法以及需要特定用例数据时的特定查找方法
- DataManagerImpl :DataManager接口的实现。 包含实际的持久性代码。 在v6中,这是唯一现在使用DbSqlSession类通过MyBatis与数据库进行通信的类。 通常,这是您要换出的班级。
- 实体 :数据接口。 仅包含getter和setter。
- EntityImpl :上述接口的实现。 在Activiti v6中,这是常规的pojo,但是该界面允许您切换到其他技术,例如带有spring-dataj,JPA,…(使用批注)的Neo4。 没有它,如果默认实现在您的持久性技术上不起作用,则需要包装/拆开实体。
合并
将所有操作移入接口可以使我们清楚地了解在代码库中分布了哪些方法。 例如,您是否知道至少有五种不同的方法来删除执行(名为“删除”,“删除”,“销毁”等)? 他们所做的几乎相同,但有细微的差别。 有时甚至一点都不微妙。
过去几周的许多工作包括将所有这些逻辑整合为一种方法。 现在,在当前的代码库中,只有一种方法可以执行某些操作。 对于想使用不同持久性技术的人来说,这非常重要。 使它们实现所有变体和微妙之处将是疯狂的。
内存中实现
为了证明持久层的可插入性,我制作了一个小的“内存中”原型。 这意味着,我们使用普通的旧式HashMaps代替关系数据库来将实体存储为{entityId,Entitys}。 然后查询成为if子句。
- 可以在Github上找到代码: https : //github.com/jbarrez/activiti-in-mem-prototype
(有时在论坛上,人们问到仅在内存中运行Activiti会有多困难,对于那些不要求使用数据库的简单用例来说。现在,这不再困难了!谁知道……这个小原型如果人们喜欢,它可能会变成东西!)
- 如预期的那样,我们用内存中的版本换出了DataManager实现,请参阅InMemoryProcessEngineConfiguration
@Overrideprotected void initDataManagers() {this.deploymentDataManager = new InMemoryDeploymentDataManager(this);this.resourceDataManager = new InMemoryResourceDataManager(this);this.processDefinitionDataManager = new InMemoryProcessDefinitionDataManager(this);this.jobDataManager = new InMemoryJobDataManager(this);this.executionDataManager = new InMemoryExecutionDataManager(this);this.historicProcessInstanceDataManager = new InMemoryHistoricProcessInstanceDataManager(this);this.historicActivityInstanceDataManager = new InMemoryHistoricActivityInstanceDataManager(this);this.taskDataManager = new InMemoryTaskDataManager(this);this.historicTaskInstanceDataManager = new InMemoryHistoricTaskInstanceDataManager(this);this.identityLinkDataManager = new InMemoryIdentityLinkDataManager(this);this.variableInstanceDataManager = new InMemoryVariableInstanceDataManager(this);this.eventSubscriptionDataManager = new InMemoryEventSubscriptionDataManager(this);}
这样的DataManager实现非常简单。 例如,请参阅InMemoryTaskDataManager ,它需要为TaskEntity实现数据检索/写入方法:
public List<TaskEntity> findTasksByExecutionId(String executionId) {List<TaskEntity> results = new ArrayList<TaskEntity>();for (TaskEntity taskEntity : entities.values()) {if (taskEntity.getExecutionId() != null && taskEntity.getExecutionId().equals(executionId)) {results.add(taskEntity);}}return results;}
为了证明它是可行的,让我们进行部署,启动一个简单的流程实例,进行一些小任务查询并检查一些历史记录。 此代码与“常规” Activiti用法完全相同。
public class Main {public static void main(String[] args) {InMemoryProcessEngineConfiguration config = new InMemoryProcessEngineConfiguration();ProcessEngine processEngine = config.buildProcessEngine();RepositoryService repositoryService = processEngine.getRepositoryService();RuntimeService runtimeService = processEngine.getRuntimeService();TaskService taskService = processEngine.getTaskService();HistoryService historyService = processEngine.getHistoryService();Deployment deployment = repositoryService.createDeployment().addClasspathResource("oneTaskProcess.bpmn20.xml").deploy();System.out.println("Process deployed! Deployment id is " + deployment.getId());ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess");List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();System.out.println("Got " + tasks.size() + " tasks!");taskService.complete(tasks.get(0).getId());System.out.println("Number of process instances = " + historyService.createHistoricProcessInstanceQuery().count());System.out.println("Number of active process instances = " + historyService.createHistoricProcessInstanceQuery().finished().count());System.out.println("Number of finished process instances = " + historyService.createHistoricProcessInstanceQuery().unfinished().count());}}
如果运行它,它可以为您提供这一切(非常快,因为它们全部在内存中!):
Process deployed! Deployment id is 27073df8-5d54-11e5-973b-a8206642f7c5Got 1 tasks!Number of process instances = 1Number of active process instances = 0Number of finished process instances = 1
在这个原型中,我没有添加事务语义。 这意味着,如果两个用户将同时完成同一用户任务,那么结果将是不确定的。 当然,您可能希望从Activiti API获得类似内存中事务的逻辑,但是我尚未实现。 基本上,您需要将所有接触的对象保留在一个小的缓存中,直到刷新/提交时间,然后在该位置进行一些锁定/同步。 当然,我确实接受拉取请求:)
下一步是什么?
好吧,这完全取决于您。 让我们知道您对此有何想法,请尝试一下!
我们与计划很快试用的社区成员/客户之一保持着密切联系。 但是我们当然也想自己玩这个游戏,我们正在寻找一个很酷的首选(我自己在Neo4j的心中仍然占有特殊的位置 ……这很适合交易)。
但是最重要的一点是:在Activiti v6中,现在可以干净地交换持久层。 我们为现在的外观感到自豪。 我们希望您也喜欢它!
翻译自: https://www.javacodegeeks.com/2015/09/pluggable-persistence-in-activiti-6.html