跳到主要内容

Flowable DMN API

Doubao 中英对照 The Flowable DMN API

DMN 引擎 API 与服务​

DMN 引擎 API 是与 Flowable DMN 交互最常用的方式。核心起点是 DmnEngine,它可以有多种创建方式,如配置章节所述。从 DmnEngine 可以获取各种其他服务。
DmnEngine 和服务对象都是线程安全的。因此你可以在整个服务器范围内持有其中某个对象的引用。

DmnEngine dmnEngine = DmnEngines.getDefaultDmnEngine();
DmnRuleService dmnRuleService = dmnEngine.getDmnRuleService();
DmnRepositoryService dmnRepositoryService = dmnEngine.getDmnRepositoryService();
DmnManagementService dmnManagementService = dmnEngine.getDmnManagementService();

DmnEngines.getDefaultDmnEngine() 在首次调用时会初始化并构建一个 DMN 引擎,之后始终返回同一个 DMN 引擎。所有 DMN 引擎的正确创建与关闭可以通过 DMNEngines.init() 和 DMNEngines.destroy() 完成。

DmnEngines 类会扫描所有 flowable.dmn.cfg.xml 和 flowable-dmn-context.xml 文件。对于所有 flowable.dmn.cfg.xml 文件,DMN 引擎将以典型的 Flowable 方式构建:DmnEngineConfiguration.createDmnEngineConfigurationFromInputStream(inputStream).buildDmnEngine()。对于所有 flowable-dmn-context.xml 文件,DMN 引擎将以 Spring 方式构建:先创建 Spring 应用上下文,然后从该应用上下文中获取 DMN 引擎。

所有服务都是无状态的。这意味着你可以轻松地在集群中的多个节点上运行 Flowable DMN,每个节点都连接同一个数据库,而无需担心之前的调用实际由哪台机器执行。无论在何处执行,对任何服务的任何调用都是幂等的。

DmnRepositoryService 可能是使用 Flowable DMN 引擎时第一个需要的服务。该服务提供用于管理和操作部署与 DMN 定义的操作。DMN 定义是 DMN 模型的根概念(DMN 的主要概念在 <dmn-introduction, DMN 介绍章节> 中说明)。它包含决策(及其决策表)的定义。
部署是 Flowable DMN 引擎内的打包单元。一个部署可以包含多个 DMN XML 文件。部署一个部署意味着将其上传到引擎,在存储到数据库之前,引擎会检查并解析所有 DMN 定义。从此时起,该部署即为系统所知,部署中包含的任何决策现在都可以执行。

此外,该服务还允许你:

  • 查询引擎已知的部署、DMN 定义和决策表。
  • 检索 DMN 定义或决策表的 POJO 版本,可使用 Java 而非 XML 对其进行内省。

DmnRuleService 提供执行决策的方法。通过提供参数和输入数据,可以启动决策的求值。

DmnManagementService 在使用 Flowable DMN 编写自定义应用时通常不需要。它允许你检索有关引擎版本、数据库表和表元数据的信息。

有关服务操作和 DMN 引擎 API 的更详细信息,参见 javadoc。

异常策略​

Flowable 中的基础异常是 org.flowable.engine.FlowableException,它是一个非受检异常。API 随时可能抛出此异常,但特定方法中发生的“预期”异常已在 javadoc 中记录。例如,以下是 DmnRuleService 的一段摘录:

/**
* Execute a decision identified by it's key.
*
* @param decisionKey the decision key, cannot be null
* @param inputVariables map with input variables
* @return the {@link RuleEngineExecutionResult} for this execution
* @throws FlowableObjectNotFoundException
* when the decision with given key does not exist.
* @throws FlowableException
* when an error occurs while executing the decision.
*/
RuleEngineExecutionResult executeDecisionByKey(String decisionKey, Map<String, Object> inputVariables);

在上面的示例中,当传入一个不存在决策的 key 时,将抛出异常。此外,由于 javadoc 明确说明 decisionKey 不能为 null,传入 null 时将抛出 FlowableIllegalArgumentException。

尽管我们希望避免庞大的异常层次结构,但在特定情况下会抛出以下子类。在流程执行或 API 调用期间发生的、不属于下列可能异常的所有其他错误,都作为常规 FlowableException 抛出。

  • FlowableOptimisticLockingException:当数据存储中因并发访问同一数据条目而发生乐观锁时抛出。
  • FlowableClassLoadingException:当请求加载的类未找到,或加载时发生错误时抛出。
  • FlowableObjectNotFoundException:当请求或操作的对象不存在时抛出。
  • FlowableIllegalArgumentException:表示在 Flowable DMN API 调用中提供了非法参数、在引擎配置中配置了非法值,或提供了非法值的异常。

查询 API​

从引擎查询数据有两种方式:查询 API 和原生查询。查询 API 允许使用流式 API 编写完全类型安全的查询。你可以向查询添加各种条件(所有条件以逻辑 AND 组合应用)以及恰好一个排序。以下代码示例:

List<DmnDeployment> dmnDeployments = dmnRepositoryService.createDeploymentQuery()
.deploymentNameLike("deployment%")
.orderByDeployTime()
.list();

有时你需要更强大的查询,例如使用 OR 操作符的查询,或无法用查询 API 表达的限制。对于这些情况,我们引入了原生查询,它允许你编写自己的 SQL 查询。返回类型由你使用的 Query 对象定义,数据会被映射到正确的对象,例如 Deployment、ProcessInstance、Execution 等。由于查询将针对数据库执行,你必须使用数据库中定义的表名和列名;这需要对内部数据结构有一定了解,建议谨慎使用原生查询。可以通过 API 检索表名,以尽量减小依赖。

long count = dmnRepositoryService.createNativeDeploymentQuery()
.sql("SELECT count(*) FROM " + dmnManagementService.getTableName(DmnDeploymentEntity.class) + " D1, "
+ dmnManagementService.getTableName(DecisionTableEntity.class) + " D2 "
+ "WHERE D1.ID_ = D2.DEPLOYMENT_ID_ "
+ "AND D1.ID_ = #{deploymentId}")
.parameter("deploymentId", deployment.getId())
.count();

单元测试​

由于 Flowable DMN 是一个可嵌入的 Java 引擎,为 DMN 定义编写单元测试就像编写常规单元测试一样简单。

Flowable 支持 JUnit Jupiter 风格的单元测试。

在 JUnit Jupiter 风格中,需要使用 org.flowable.dmn.engine.test.FlowableDmnTest 注解
或手动注册 org.flowable.dmn.engine.test.FlowableDmnExtension。
FlowableDmnTest 注解只是一个元注解,它完成 FlowableDmnExtension 的注册
(即它做了 @ExtendWith(FlowableDmnExtension.class))。
这将使 DmnEngine 和服务作为参数可用到测试和生命周期方法中
(@BeforeAll、@BeforeEach、@AfterEach、@AfterAll)。
每个测试之前,dmnEngine 默认使用 classpath 上的 flowable.dmn.cfg.xml 资源初始化。
要指定不同的配置文件,需要使用 org.flowable.dmn.engine.test.DmnConfigurationResource 注解(见第二个示例)。
当配置资源相同时,Dmn 引擎会在多个单元测试之间静态缓存。

通过使用 FlowableDmnExtension,你可以用 org.flowable.dmn.engine.test.DmnDeployment
或 org.flowable.dmn.engine.test.DmnDeploymentAnnotation 标注测试方法。
如果同时使用了 @DmnDeployment 和 @DmnDeploymentAnnotation,则 @DmnDeployment
优先,@DmnDeploymentAnnotation 将被忽略。
当测试方法标注了 @DmnDeployment 时,
每个测试之前将部署 DmnDeployment#resources 中定义的 dmn 文件。
如果未定义资源,则部署与测试类同包下、形式为 testClassName.testMethod.dmn 的资源文件。
测试结束时,将删除该部署,包括所有相关的 dmn 定义、执行等。
更多信息参见 DmnDeployment 类。

综合考虑以上,一个 JUnit Jupiter 测试如下所示:

使用默认资源的 JUnit Jupiter 测试。

@FlowableDmnTest
class MyDecisionTableTest {

@Test
@DmnDeploymentAnnotation
void simpleDmnTest(DmnEngine dmnEngine) {
DmnRuleService dmnRuleService = dmnEngine.getDmnRuleService();

Map<String, Object> executionResult = ruleService.createExecuteDecisionBuilder()
.decisionKey("extensionUsage")
.variable("inputVariable1", 2)
.variable("inputVariable2", "test2")
.executeWithSingleResult();

Assertions.assertThat(executionResult).containsEntry("output1", "test1");
}
}

在 JUnit Jupiter 中,你还可以将部署的 id(使用 +org.flowable.dmn.engine.test.DmnDeploymentId+_)注入到测试和生命周期方法中。

使用自定义资源的 JUnit Jupiter 测试。

@FlowableDmnTest
@DmnConfigurationResource("flowable.custom.dmn.cfg.xml")
class MyDecisionTableTest {

@Test
@DmnDeploymentAnnotation
void simpleDmnTest(DmnEngine dmnEngine) {
DmnRuleService dmnRuleService = dmnEngine.getDmnRuleService();

Map<String, Object> executionResult = ruleService.createExecuteDecisionBuilder()
.decisionKey("extensionUsage")
.variable("inputVariable1", 2)
.variable("inputVariable2", "test2")
.executeWithSingleResult();

Assertions.assertThat(executionResult).containsEntry("output1", "test1");
}
}

Web 应用中的 DMN 引擎​

DmnEngine 是一个线程安全的类,可以轻松地在多个线程之间共享。在 web 应用中,这意味着可以在容器启动时创建一次 DMN 引擎,并在容器关闭时关闭该引擎。

以下代码片段展示了如何编写一个简单的 ServletContextListener,在纯 Servlet 环境中初始化和销毁流程引擎:

public class DmnEnginesServletContextListener implements ServletContextListener {

public void contextInitialized(ServletContextEvent servletContextEvent) {
DmnEngines.init();
}

public void contextDestroyed(ServletContextEvent servletContextEvent) {
DmnEngines.destroy();
}

}

contextInitialized 方法会委托给 DmnEngines.init()。这会在 classpath 上查找 flowable.dmn.cfg.xml 资源文件,并为给定配置创建一个 DmnEngine(例如,多个带配置文件的 JAR)。如果 classpath 上有多个这样的资源文件,请确保它们都使用不同的名称。需要 DMN 引擎时,可以这样获取:

DmnEngines.getDefaultDmnEngine()

或者:

DmnEngines.getDmnEngine("myName");

当然,也可以使用创建 DMN 引擎的任意变体,
如配置章节所述。

上下文监听器的 contextDestroyed 方法委托给 DmnEngines.destroy()。这将正确关闭所有已初始化的 DMN 引擎。