Skip to content

Commit c0c33f5

Browse files
author
Dmytro Gundartsev
committed
fix: Introduced unanimous single valued output in decision evaluation result
1 parent ac7d891 commit c0c33f5

File tree

8 files changed

+164
-21
lines changed

8 files changed

+164
-21
lines changed

api/src/main/kotlin/dev/bpmcrafters/processengineapi/decision/CollectDecisionEvaluationResult.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package dev.bpmcrafters.processengineapi.decision
22

33
/**
44
* Decision evaluation result for all collect-valued hit policies.
5-
* @since 2.0
5+
* @since 1.4
66
*/
77
data class CollectDecisionEvaluationResult(
88
val result: List<DecisionEvaluationOutput>
9-
) : DecisionEvaluationResult
9+
) : DecisionEvaluationResult()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package dev.bpmcrafters.processengineapi.decision
2+
3+
/**
4+
* Decision evaluation output representing multiple named values
5+
* produced by a decision with multiple outputs.
6+
*
7+
* @since 1.4
8+
*/
9+
data class DecisionEvaluationMultiOutput (
10+
val outputs: Map<String, Any>
11+
12+
) :DecisionEvaluationOutput

api/src/main/kotlin/dev/bpmcrafters/processengineapi/decision/DecisionEvaluationOutput.kt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,23 @@ package dev.bpmcrafters.processengineapi.decision
22

33
/**
44
* Decision output.
5-
* @since 2.0
5+
* @since 1.4
66
*/
7-
data class DecisionEvaluationOutput(
7+
8+
sealed interface DecisionEvaluationOutput {
89
/**
9-
* Keys are output names pointing to values.
10+
* Returns as a single output value
1011
*/
11-
val values: Map<String, Any>,
12+
fun single(): DecisionEvaluationSingleOutput {
13+
require(this is DecisionEvaluationSingleOutput) { "Decision evaluation single output expected but it was ${this::class.simpleName}" }
14+
return this
15+
}
16+
1217
/**
13-
* Additional metadata about the task.
18+
* Returns as multi-outputs with names
1419
*/
15-
val meta: Map<String, String> = emptyMap()
16-
)
20+
fun many(): DecisionEvaluationMultiOutput {
21+
require(this is DecisionEvaluationMultiOutput) { "Decision evaluation multi output expected but it was ${this::class.simpleName}" }
22+
return this
23+
}
24+
}

api/src/main/kotlin/dev/bpmcrafters/processengineapi/decision/DecisionEvaluationResult.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package dev.bpmcrafters.processengineapi.decision
22

33
/**
44
* Represents result of decision evaluation.
5+
*
56
*/
6-
sealed interface DecisionEvaluationResult {
7+
sealed class DecisionEvaluationResult {
78
/**
89
* Returns the result as single.
910
*/
@@ -19,4 +20,9 @@ sealed interface DecisionEvaluationResult {
1920
require(this is CollectDecisionEvaluationResult) { "Decision evaluation result must be a collect but it was ${this::class.simpleName}" }
2021
return this
2122
}
23+
24+
/**
25+
* Additional metadata on evaluation result.
26+
*/
27+
val meta: Map<String, String> = emptyMap()
2228
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.bpmcrafters.processengineapi.decision
2+
3+
/**
4+
* Decision evaluation output representing a single anonymous output
5+
*
6+
* @since 1.4
7+
*/
8+
data class DecisionEvaluationSingleOutput(
9+
val output: Any?
10+
): DecisionEvaluationOutput

api/src/main/kotlin/dev/bpmcrafters/processengineapi/decision/SingleDecisionEvaluationResult.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ package dev.bpmcrafters.processengineapi.decision
66
*/
77
data class SingleDecisionEvaluationResult(
88
val result: DecisionEvaluationOutput
9-
) : DecisionEvaluationResult
9+
) : DecisionEvaluationResult()

api/src/test/kotlin/dev/bpmcrafters/processengineapi/decision/DecisionUseCase.kt

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,64 @@ internal class DecisionUseCase(
2525
).get()
2626
.single()
2727
.result
28-
.values["discount"] as Double
28+
.single().output as Double
2929
}
3030

31+
fun calculateCustomerOffer(customerStatus: CustomerStatus, year: Int): Offer {
32+
return decisionApi.evaluateDecision(
33+
DecisionByRefEvaluationCommand(
34+
decisionRef = "customerOffer",
35+
payloadSupplier = PayloadSupplier {
36+
mapOf<String, Any>(
37+
"customerStatus" to customerStatus,
38+
"year" to year
39+
)
40+
},
41+
restrictionSupplier = Supplier {
42+
mapOf<String, String>(CommonRestrictions.TENANT_ID to "tenant-1")
43+
}
44+
)
45+
).get()
46+
.single()
47+
.result
48+
.many()
49+
.outputs
50+
.let { Offer (it["id"] as Integer, it["name"] as String) }
51+
}
52+
53+
fun calculateCustomerOffers(customerStatus: CustomerStatus, year: Int): List<Offer> {
54+
return decisionApi.evaluateDecision(
55+
DecisionByRefEvaluationCommand(
56+
decisionRef = "customerOffers",
57+
payloadSupplier = PayloadSupplier {
58+
mapOf<String, Any>(
59+
"customerStatus" to customerStatus,
60+
"year" to year
61+
)
62+
},
63+
restrictionSupplier = Supplier {
64+
mapOf<String, String>(CommonRestrictions.TENANT_ID to "tenant-1")
65+
}
66+
)
67+
).get()
68+
.collect()
69+
.result
70+
.map {
71+
it.many()
72+
.outputs
73+
.let { Offer (it["id"] as Integer, it["name"] as String) }
74+
}
75+
}
76+
77+
3178
enum class CustomerStatus {
3279
SILVER,
3380
GOLD,
3481
PLATINUM
3582
}
83+
84+
data class Offer (
85+
val id: Integer,
86+
val name: String
87+
)
3688
}

docs/decision-api.md

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@
22
title: Decision Evaluation API
33
---
44

5-
The Decision Evaluation API provides functionality to evaluate DMN decision. The API returns a generic
6-
`DecisionEvaluationResult` which can be cast to single or collect result with the corresponding method.
5+
The Decision Evaluation API provides functionality to evaluate a DMN decision. The API returns a generic
6+
`DecisionEvaluationResult` which can be cast to a single or collect result using the corresponding method:
77

8-
And here is the example code to evaluate decision:
8+
- `single()` → returns `SingleDecisionEvaluationResult`
9+
- `collect()` → returns `CollectDecisionEvaluationResult`
10+
11+
Each of those wraps a `DecisionEvaluationOutput` value which can be either:
12+
13+
- `DecisionEvaluationSingleOutput` for context-less single values (access via `single().getOutput()`), or
14+
- `DecisionEvaluationMultiOutput` for named/context values (access via `many().getOutputs()`).
15+
16+
Below are example snippets in Java showing the new API usage.
917

1018
```java
1119
class DecisionUseCase {
@@ -19,7 +27,7 @@ class DecisionUseCase {
1927
* @return calculated discount.
2028
*/
2129
public Double evaluateDiscount(CustomerStatus customerStatus, Integer year) {
22-
return (Double)evaluateDecisionApi.evaluateDecision(
30+
return (Double) evaluateDecisionApi.evaluateDecision(
2331
new DecisionByRefEvaluationCommand(
2432
"customerDiscount",
2533
() -> Map.of(
@@ -28,11 +36,58 @@ class DecisionUseCase {
2836
),
2937
Map.of(CommonRestrictions.TENANT_ID, "myTenant")
3038
)
31-
).get()
32-
.single()
33-
.getResult()
34-
.get("discount")
35-
;
39+
).get() // DecisionEvaluationResult
40+
.single() // SingleDecisionEvaluationResult
41+
.getResult() // DecisionEvaluationOutput
42+
.single() // DecisionEvaluationSingleOutput
43+
.getOutput(); // actual value, e.g. Double
44+
}
45+
46+
/**
47+
* Calculates a customer offer with multiple named outputs (multi-output decision).
48+
*/
49+
public Offer evaluateOffer(CustomerStatus customerStatus, Integer year) {
50+
Map<String, Object> outputs = evaluateDecisionApi.evaluateDecision(
51+
new DecisionByRefEvaluationCommand(
52+
53+
"customerOffer",
54+
() -> Map.of(
55+
"customerStatus", customerStatus,
56+
"registrationYear", year
57+
),
58+
Map.of(CommonRestrictions.TENANT_ID, "myTenant")
59+
)
60+
).get() // DecisionEvaluationResult
61+
.single() // SingleDecisionEvaluationResult
62+
.getResult() // DecisionEvaluationOutput
63+
.many() // DecisionEvaluationMultiOutput
64+
.getOutputs(); // Map<String, Object>
65+
66+
return new Offer((Integer) outputs.get("id"), (String) outputs.get("name"));
67+
}
68+
69+
/**
70+
* Calculates multiple customer offers (collect decision result).
71+
*/
72+
public List<Offer> evaluateOffers(CustomerStatus customerStatus, Integer year) {
73+
return evaluateDecisionApi.evaluateDecision(
74+
new DecisionByRefEvaluationCommand(
75+
"customerOffers",
76+
() -> Map.of(
77+
"customerStatus", customerStatus,
78+
"registrationYear", year
79+
),
80+
Map.of(CommonRestrictions.TENANT_ID, "myTenant")
81+
)
82+
).get() // DecisionEvaluationResult
83+
.collect() // CollectDecisionEvaluationResult
84+
.getResult() // List<DecisionEvaluationOutput>
85+
.stream()
86+
.map(o -> {
87+
Map<String, Object> outputs = o.many().getOutputs();
88+
return new Offer((Integer) outputs.get("id"), (String) outputs.get("name"));
89+
})
90+
.toList();
3691
}
3792
}
3893

0 commit comments

Comments
 (0)