Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.util.function.Supplier

/**
* Command to evaluate decision by provided reference.
* @since 2.0
* @since 1.4
*/
data class DecisionByRefEvaluationCommand(
/**
Expand All @@ -20,4 +20,18 @@ data class DecisionByRefEvaluationCommand(
* Restrictions supplier to pass to this evaluation.
*/
val restrictionSupplier: Supplier<Map<String, String>>
) : DecisionEvaluationCommand, PayloadSupplier by payloadSupplier
) : DecisionEvaluationCommand, PayloadSupplier by payloadSupplier {

/**
* Constructs an evaluate command.
* @param decisionRef decision reference.
* @param payload payload to use.
* @param restrictions restrictions for the message.
*/
constructor(decisionRef: String, payload: Map<String, Any>, restrictions: Map<String, String>) : this(
decisionRef = decisionRef,
payloadSupplier = PayloadSupplier { payload },
restrictionSupplier = { restrictions }
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import dev.bpmcrafters.processengineapi.PayloadSupplier

/**
* Interface for decision evaluation commands.
* @since 2.0
* @since 1.4
*/
interface DecisionEvaluationCommand : PayloadSupplier
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@ package dev.bpmcrafters.processengineapi.decision

/**
* Decision output.
* @since 2.0
*
* Represents a result of evaluation. Might be handled
* @since 1.4
*/
data class DecisionEvaluationOutput(
interface DecisionEvaluationOutput {

/**
* Keys are output names pointing to values.
* Returns as a single output value converted to a type.
* This conversion is an attempt to convert the output to the given type and might fail, if the type is incompatible.
* @param type class which the output will be cast to
*/
val values: Map<String, Any>,
fun <T: Any> asType(type: Class<T>): T?

/**
* Returns as multi-output map, keyed by output name. Attempt on converting single output value into Map would result in a runtime exception
*/
fun asMap(): Map<String, Any?>?

/**
* Additional metadata about the task.
* Additional metadata on evaluation output, if supported by the engine.
*/
val meta: Map<String, String> = emptyMap()
)
fun meta(): Map<String, String> = mapOf()
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package dev.bpmcrafters.processengineapi.decision

/**
* Represents result of decision evaluation.
* Represents the result of decision evaluation.
* @since 1.4
*/
sealed interface DecisionEvaluationResult {
interface DecisionEvaluationResult {
/**
* Returns the result as single.
* Returns the result excepted to be a single value.
*
* This is because the hit policy defined it to be single (single result or result of aggregation).
*/
fun single(): SingleDecisionEvaluationResult {
require(this is SingleDecisionEvaluationResult) { "Decision evaluation result must be a single but it was ${this::class.simpleName}" }
return this
}
fun asSingle(): DecisionEvaluationOutput

/**
* Returns the result as collect.
* Returns the result expected to be a collection of values.
*
* This is because multiple rules have fired, and we collect multiple results without aggregation.
*/
fun collect(): CollectDecisionEvaluationResult {
require(this is CollectDecisionEvaluationResult) { "Decision evaluation result must be a collect but it was ${this::class.simpleName}" }
return this
}
fun asList(): List<DecisionEvaluationOutput>

/**
* Additional metadata on evaluation result, if supported by the engine.
*/
fun meta(): Map<String, String>
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ interface EvaluateDecisionApi : RestrictionAware, MetaInfoAware {
/**
* Evaluate decision.
* @param command a command containing parameter for decision evaluation.
* @return decision evaluation result. Depending on the hit policy might either [SingleDecisionEvaluationResult]
* or [CollectDecisionEvaluationResult].
* @return decision evaluation result.
*/
fun evaluateDecision(command: DecisionEvaluationCommand): CompletableFuture<DecisionEvaluationResult>
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package dev.bpmcrafters.processengineapi.decision

import dev.bpmcrafters.processengineapi.CommonRestrictions
import dev.bpmcrafters.processengineapi.PayloadSupplier
import java.util.function.Supplier

/**
* Example use cae to demonstrate the usage of the API.
* @since 1.4
*/
internal class DecisionUseCase(
private val decisionApi: EvaluateDecisionApi
) {
Expand All @@ -12,25 +14,71 @@ internal class DecisionUseCase(
return decisionApi.evaluateDecision(
DecisionByRefEvaluationCommand(
decisionRef = "customerDiscount",
payloadSupplier = PayloadSupplier {
mapOf<String, Any>(
"customerStatus" to customerStatus,
"year" to year
payload = mapOf(
"customerStatus" to customerStatus,
"year" to year
),
restrictions = mapOf(
CommonRestrictions.TENANT_ID to "tenant-1"
)
)
).get()
.asSingle()
.asType(Double::class.java)
?: NO_DISCOUNT
}

fun calculateCustomerOffer(customerStatus: CustomerStatus, year: Int): Offer {
return decisionApi.evaluateDecision(
DecisionByRefEvaluationCommand(
decisionRef = "customerOffer",
payload = mapOf(
"customerStatus" to customerStatus,
"year" to year
),
restrictions = mapOf(
CommonRestrictions.TENANT_ID to "tenant-1"
)
)
).get()
.asSingle()
.asMap()
?.let { Offer(it["id"] as Integer, it["name"] as String) }
?: throw IllegalStateException("No offer found")
}

fun calculateCustomerOffers(customerStatus: CustomerStatus, year: Int): List<Offer> {
return decisionApi.evaluateDecision(
DecisionByRefEvaluationCommand(
decisionRef = "customerOffers",
payload = mapOf(
"customerStatus" to customerStatus,
"year" to year
),
restrictions =
mapOf(
CommonRestrictions.TENANT_ID to "tenant-1"
)
},
restrictionSupplier = Supplier {
mapOf<String, String>(CommonRestrictions.TENANT_ID to "tenant-1")
}
)
).get()
.single()
.result
.values["discount"] as Double
.asList()
.mapNotNull { result -> result
.asMap()
?.let { Offer(it["id"] as Integer, it["name"] as String) } }
}

enum class CustomerStatus {
SILVER,
GOLD,
PLATINUM
}

data class Offer(
val id: Integer,
val name: String
)

companion object {
const val NO_DISCOUNT = 0.0
}
}
63 changes: 54 additions & 9 deletions docs/decision-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
title: Decision Evaluation API
---

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

And here is the example code to evaluate decision:
- `asSingle()` → returns `DecisionEvaluationOutput`
- `asList()` → returns `List<DecisionEvaluationOutput>`

Below are example snippets in Java showing the new API usage.

```java
class DecisionUseCase {
Expand All @@ -19,7 +22,7 @@ class DecisionUseCase {
* @return calculated discount.
*/
public Double evaluateDiscount(CustomerStatus customerStatus, Integer year) {
return (Double)evaluateDecisionApi.evaluateDecision(
return (Double) evaluateDecisionApi.evaluateDecision(
new DecisionByRefEvaluationCommand(
"customerDiscount",
() -> Map.of(
Expand All @@ -28,11 +31,53 @@ class DecisionUseCase {
),
Map.of(CommonRestrictions.TENANT_ID, "myTenant")
)
).get()
.single()
.getResult()
.get("discount")
;
).get() // DecisionEvaluationResult
.asSingle() // DecisionEvaluationOutput
.asType(Double.class); // convert to double
}

/**
* Calculates a customer offer with multiple named outputs (multi-output decision).
*/
public Offer evaluateOffer(CustomerStatus customerStatus, Integer year) {
Map<String, Object> outputs = evaluateDecisionApi.evaluateDecision(
new DecisionByRefEvaluationCommand(

"customerOffer",
() -> Map.of(
"customerStatus", customerStatus,
"registrationYear", year
),
Map.of(CommonRestrictions.TENANT_ID, "myTenant")
)
).get() // DecisionEvaluationResult
.asSingle() // DecisionEvaluationOutput
.asMap(); // Map<String, Object>

return new Offer((Integer) outputs.get("id"), (String) outputs.get("name"));
}

/**
* Calculates multiple customer offers (collect decision result).
*/
public List<Offer> evaluateOffers(CustomerStatus customerStatus, Integer year) {
return evaluateDecisionApi.evaluateDecision(
new DecisionByRefEvaluationCommand(
"customerOffers",
Map.of(
"customerStatus", customerStatus,
"registrationYear", year
),
Map.of(CommonRestrictions.TENANT_ID, "myTenant")
)
).get() // DecisionEvaluationResult
.asList() // List<DecisionEvaluationOutput>
.stream()
.map(o -> {
Map<String, Object> outputs = o.asMap();
return new Offer((Integer) outputs.get("id"), (String) outputs.get("name"));
})
.toList();
}
}

Expand Down