-
Notifications
You must be signed in to change notification settings - Fork 565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Business rule task can define decisionId as expression #8822
Conversation
A CalledDecision with an expression as its decisionId should be able to call the decision based on the evaluation result of the expression. For example, if the decisionId is an expression `= decisionId`, then the value of the `decisionId` variable should be used as the id of the decision that is called. The test can verify that it works by asserting that a result variable has been created for the process instance. Note that no decision with id `=decisionIdVariable` exists, so the test fails until the evaluation result of the expression is used, instead of the static value.
An incident should be raised when the decision id expression fails to be evalutated successfully. The error type should be EXTRACT_VALUE_ERROR like the other expression evaluation failures. In addition, it should be possible to resolve the incident by removing the problem (i.e. set the missing variable in this case).
The decisionId will become a calculated value. To reuse it, lets first extract it into a local variable.
The decisionId is validated to be valid expression (or static value) by the ZeebeRuntimeValidators. So we can safely transform it to an expression by parsing it using the expression language. To keep the behavior the same, the BpmnDecisionBehavior needs to extract the original string representation of the expression. For static values, this is simply the static value again. For expressions it would be the entire expression in string representation. Since expressions are not yet evaluated, this keeps the logic the same from a user's perspective. But the decisionId is now additionally parsed to allow evaluation to be added later.
@remcowesterhoud Please have a look at this one as well 🙇 It's probably easiest to review this on a per-commit basis. Please let me know if anything is unclear. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@korthout I have a small suggestions and some questions. It's all small stuf, for the rest it LGTM 👍
❓ I'm assuming the Feel engine will decide if it is actually an expression or not (i.e. starts with an =
) and just returns the String if it does not start with an =
?
.../io/camunda/zeebe/engine/processing/deployment/model/element/ExecutableBusinessRuleTask.java
Show resolved
Hide resolved
...va/io/camunda/zeebe/engine/processing/deployment/model/element/ExecutableCalledDecision.java
Show resolved
Hide resolved
...va/io/camunda/zeebe/engine/processing/deployment/model/element/ExecutableCalledDecision.java
Show resolved
Hide resolved
...da/zeebe/engine/processing/deployment/model/transformer/zeebe/CalledDecisionTransformer.java
Show resolved
Hide resolved
final var decisionId = element.getDecisionId().getExpression(); | ||
final var decisionIdOrFailure = evalDecisionIdExpression(element, scopeKey); | ||
if (decisionIdOrFailure.isLeft()) { | ||
return decisionIdOrFailure.map(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Why does this return a decisionIfOrFailure.map(null)
? It seems like this is just a trick to fix types of the Either
, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that is correct. The type of decisionIdOrFailure is Either<Failure, String>
but the return type expects an Either<Failure, DecisionEvaluationResult>
.
The only alternative I know is to wrap the left's value into a new left, but that's less functional. I this case it would be return Either.left(decisionIdOrFailure.getLeft());
.
After having worked with Eithers for some time, I'm still not sure which I like better. My preference goes slightly to the map. The map looks strange at first but is really concise and accurate. We simply map the right side of a left to the correct type and null
makes it clear that we don't know its value. But feel free to counter this. I'm also open to other ideas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As someone who has barely worked with Either
the map seems very weird. You're mapping something, but because it's a left you won't actually map anything. I would expect this code to throw a NPE at first since it seems like it applies the given function to something, but in reality the entire map()
method doesn't do anything because it's a left. So I think my preference goes to Either.left(decisionIdOrFailure.getLeft())
. Then again it's not a very strong preference since it's all documented quite well.
A separate method which would do this for us to make it more readable would be nice. But the problem is that it would then be on Either
, and Right
would need an implementation of it which is not really possible. It's a hard problem 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I do like the more functional approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, you're absolutely right. And it should be clear for first-time readers. I've changed it to the non-functional style.
I've also looked around our codebase for similar situations and found 2 cases that used Either.left(x.getLeft()) and 1 case that uses map. 😆 Nothing really helpful. This other map case can't be changed though, since it's really mapping a right as well. See https://github.com/camunda-cloud/zeebe/blob/main/engine/src/main/java/io/camunda/zeebe/engine/processing/common/CatchEventBehavior.java#L104
Yes, that is correct. When you call the evaluateExpression on the expression language it executes some real simple logic that handles the case that the expression actually failed to parse, and immediately returns the static value as that was the result of parsing it. https://github.com/camunda-cloud/zeebe/blob/main/expression-language/src/main/java/io/camunda/zeebe/el/impl/FeelExpressionLanguage.java#L74-L85 ❓ Should I add this knowledge to any of the specific commits? |
I don't think you need to add it to any commit. I fully expected this was the case, I just couldn't quite pinpoint where it happened. Thanks! |
This adds evaluation of the decision id expression to the evaluateDecision logic of the BpmnDecisionBehavior. From now on, if a CalledDecision has a decisionId that is prefixed by the expression symbol (`=`), then the expression is evaluated. Otherwise, its used as a static value. If the decision evaluation fails, a Left<Failure, ?> is returned which can be used to raise an incident. This is already the case for the business rule task.
e73edc7
to
d1f8c49
Compare
bors merge |
Description
A business rule task with a called decision needs to specify a decisionId for the decision that is called. It should be possible to refer to a decision based on the evaluation result of an expression. So, it should be possible to define the decisionId as an expression. See #8779.
This adds support to define the
decisionId
of azeebe:calledDecision
as an expression (i.e. prefixed with=
; e.g.= variableContainingDecisionId
), while maintaining the ability to define it as a static value (i.e. not prefixed by=
; e.g.an_actual_decision_id
). For example, if thedecisionId
is an expression= variableContainingDecisionId
, then the value of thevariableContainingDecisionId
variable should be used as the id of the decision that is called.The
decisionId
expression is evaluated during the activation of the business rule task, just after input mapping and just before the decision is evaluated. This means you can use the result of input mappings in thedecisionId
expression.If the evaluation fails, an incident of type
EXTRACT_VALUE_ERROR
is raised, similar to other expression evaluation failures. Resolving the incident will re-apply input mappings, re-evaluate thedecisionId
expression and then call the decision.Related issues
closes #8779
Definition of Done
Not all items need to be done depending on the issue and the pull request.
Code changes:
backport stable/1.3
) to the PR, in case that fails you need to create backports manually.Testing:
Documentation: