Comments (9)
@lastchiliarch does my above proposal help?
Thanks for your help and hard work. It's diffcult for the "validate supervisor result" task to control the flow, seems message event could be a more graceful approach.
from lib-bpmn-engine.
Hi @lastchiliarch
if I got you right, then you want to go backward, like in a loop?
I mean basically, what does this loop BPMN shown below?
That is likely not supported in the current version ... but I will think about, how to implement it 👍
Thank you for your feedback.
Also, PRs are always welcome :)
from lib-bpmn-engine.
Yes, that's what i want. I'm new to bpmn so the implement is diffcult for me . But I'd like to help doing a part of job of this one or anything I can.
from lib-bpmn-engine.
I've implemented a test, that show "backwards" works in this simple loop example above.
See
from lib-bpmn-engine.
I re-read your initial post and I have to admit, I don't understand your business requirement.
What I can say, BPMN does not support "reset" == if you design some condition after the "confirm receipt",
the bpmn flow will continue a second round.
This is likeley not what you want in business, I guess.
I would rather suggest, to model a separate BPMN process for refunding.
A typical refind process could look like:
- find existing order process
- cancel existing order process (lib-bpmn-engine does not support cancelling yet, but BPMN itself is desgined for that)
- validate refund conditions
- subtract refund fees
- make payment order, so the user gets its money back
The benefit of having two processes are
- more transparency, what was happen, when you "link" the processes by their keys
- easier to understand
- easier to distribute
How does this resonate with you?
from lib-bpmn-engine.
re-read your initial post and I have to admit, I don't understand your business requirement.
What I can say, BPMN does not support "reset" == if you design some condition after the "confirm receipt",
the bpmn flow will continue a second round.
Yes, I want reset function.
Some of my workflow likes below.
- user try to apply some auth
- reviewer audit the application and tag some label like city
- supervisor make the final confirm
- if supervisor think some label is not correct , he will ask the reviewer to re-audit it.
- reviewr re-aduit the tag updated label
- supervisor check the applcation again like step 4
- approve the auth if everything goes well
Since the enginer does not support reset, it will be traped into infinite loop if I don't clear the op variable in notifySupervisor.
So What I want is clearing the processInfo where event is trigged in backwards case.
If bpmn is not designed to support this, I'd like to have some functiones to clear or delete the processInfo variables manually to archive this goal.
Some is some of my test code and bpmn file.
var (
bpmnEngine = New("auth_apply")
process *ProcessInfo
instance *ProcessInstanceInfo
)
func Test_apply_auth_loop(t *testing.T) {
// setup
var err error
process, err = bpmnEngine.LoadFromFile("../../test-cases/auth_apply.bpmn")
bpmnEngine.AddTaskHandler("apply_auth", applyAuth)
bpmnEngine.AddTaskHandler("audit", audit)
bpmnEngine.AddTaskHandler("prove_auth", proveAuth)
bpmnEngine.AddTaskHandler("notify_supervisor", notifySupervisor)
instance, err = bpmnEngine.CreateInstance(process.ProcessKey, nil)
fmt.Println(err)
bpmnEngine.RunOrContinueInstance(instance.GetInstanceKey())
then.AssertThat(t, instance.GetVariable("city"), is.EqualTo("Beijing"))
then.AssertThat(t, instance.state, is.EqualTo(process_instance.COMPLETED))
}
func applyAuth(job ActivatedJob) {
log.Println("start to applyAuth")
job.Complete()
}
func audit(job ActivatedJob) {
log.Println("start to audit")
op := job.GetVariable("op")
if op == nil {
job.SetVariable("city", "London")
bpmnEngine.PublishEventForInstance(instance.GetInstanceKey(), "audit_by_reviewer")
job.Complete()
return
}
auditop := op.(string)
if auditop != "" {
job.SetVariable("city", "Beijing")
bpmnEngine.PublishEventForInstance(instance.GetInstanceKey(), "audit_by_reviewer")
job.Complete()
} else {
job.SetVariable("city", "London")
bpmnEngine.PublishEventForInstance(instance.GetInstanceKey(), "audit_by_reviewer")
job.Complete()
}
}
func proveAuth(job ActivatedJob) {
log.Println("start to proveAuth")
job.Complete()
}
func notifySupervisor(job ActivatedJob) {
log.Println("start to notifySupervisor")
city := job.GetVariable("city").(string)
if city != "Beijing" {
job.SetVariable("op", "re-audit")
} else {
job.SetVariable("op", "")
}
bpmnEngine.PublishEventForInstance(instance.GetInstanceKey(), "confirm_by_supervisor")
job.Complete()
}
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1l2k2zy" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.2.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.0.0">
<bpmn:process id="Process_16ue9kj" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0fwz2ln</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="prove_auth" name="prove_auth">
<bpmn:extensionElements>
<zeebe:taskDefinition type="prove_auth" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_0bqwngy</bpmn:incoming>
<bpmn:outgoing>Flow_0l4cgrv</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="Event_0jv5wxq">
<bpmn:incoming>Flow_0l4cgrv</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0bqwngy" sourceRef="Gateway_1dmvvpa" targetRef="prove_auth" />
<bpmn:serviceTask id="apply_auth" name="apply_auth">
<bpmn:extensionElements>
<zeebe:taskDefinition type="apply_auth" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_0fwz2ln</bpmn:incoming>
<bpmn:outgoing>Flow_1lr5ur1</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:intermediateCatchEvent id="confirm_by_supervisor" name="confirm_by_supervisor">
<bpmn:incoming>Flow_177ps1y</bpmn:incoming>
<bpmn:outgoing>Flow_0wvvvjc</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_1mr6cvo" messageRef="Message_1agfbps" />
</bpmn:intermediateCatchEvent>
<bpmn:intermediateCatchEvent id="audit_by_reviewer" name="audit_by_reviewer">
<bpmn:incoming>Flow_1grwq17</bpmn:incoming>
<bpmn:outgoing>Flow_11yboz4</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_0j6i8wh" messageRef="Message_27ugmpv" />
</bpmn:intermediateCatchEvent>
<bpmn:sequenceFlow id="Flow_0wvvvjc" sourceRef="confirm_by_supervisor" targetRef="Gateway_1dmvvpa" />
<bpmn:parallelGateway id="Gateway_07ivcgb">
<bpmn:incoming>Flow_11yboz4</bpmn:incoming>
<bpmn:outgoing>Flow_06la618</bpmn:outgoing>
</bpmn:parallelGateway>
<bpmn:sequenceFlow id="Flow_11yboz4" sourceRef="audit_by_reviewer" targetRef="Gateway_07ivcgb" />
<bpmn:eventBasedGateway id="Gateway_1t2zx79">
<bpmn:incoming>Flow_0e397yl</bpmn:incoming>
<bpmn:outgoing>Flow_1grwq17</bpmn:outgoing>
</bpmn:eventBasedGateway>
<bpmn:sequenceFlow id="Flow_1grwq17" sourceRef="Gateway_1t2zx79" targetRef="audit_by_reviewer" />
<bpmn:serviceTask id="notify_supervisor" name="notify_supervisor">
<bpmn:extensionElements>
<zeebe:taskDefinition type="notify_supervisor" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_06la618</bpmn:incoming>
<bpmn:outgoing>Flow_0c8hcd4</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:sequenceFlow id="Flow_06la618" sourceRef="Gateway_07ivcgb" targetRef="notify_supervisor" />
<bpmn:eventBasedGateway id="Gateway_00r4evy">
<bpmn:incoming>Flow_0c8hcd4</bpmn:incoming>
<bpmn:outgoing>Flow_177ps1y</bpmn:outgoing>
</bpmn:eventBasedGateway>
<bpmn:sequenceFlow id="Flow_0c8hcd4" sourceRef="notify_supervisor" targetRef="Gateway_00r4evy" />
<bpmn:sequenceFlow id="Flow_177ps1y" sourceRef="Gateway_00r4evy" targetRef="confirm_by_supervisor" />
<bpmn:sequenceFlow id="Flow_0l4cgrv" sourceRef="prove_auth" targetRef="Event_0jv5wxq" />
<bpmn:sequenceFlow id="Flow_1lr5ur1" sourceRef="apply_auth" targetRef="audit" />
<bpmn:sequenceFlow id="Flow_0e397yl" sourceRef="audit" targetRef="Gateway_1t2zx79" />
<bpmn:sequenceFlow id="re_audit" name="op='re-audit"" sourceRef="Gateway_1dmvvpa" targetRef="audit">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=op matches "re-audit"</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:serviceTask id="audit" name="audit">
<bpmn:extensionElements>
<zeebe:taskDefinition type="audit" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_1lr5ur1</bpmn:incoming>
<bpmn:incoming>re_audit</bpmn:incoming>
<bpmn:outgoing>Flow_0e397yl</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:sequenceFlow id="Flow_0fwz2ln" sourceRef="StartEvent_1" targetRef="apply_auth" />
<bpmn:exclusiveGateway id="Gateway_1dmvvpa">
<bpmn:incoming>Flow_0wvvvjc</bpmn:incoming>
<bpmn:outgoing>Flow_0bqwngy</bpmn:outgoing>
<bpmn:outgoing>re_audit</bpmn:outgoing>
</bpmn:exclusiveGateway>
</bpmn:process>
<bpmn:message id="Message_27ugmpv" name="audit_by_reviewer">
<bpmn:extensionElements>
<zeebe:subscription correlationKey="=key" />
</bpmn:extensionElements>
</bpmn:message>
<bpmn:message id="Message_067eof3" name="Message_067eof3">
<bpmn:extensionElements>
<zeebe:subscription correlationKey="=key" />
</bpmn:extensionElements>
</bpmn:message>
<bpmn:message id="Message_1agfbps" name="confirm_by_supervisor">
<bpmn:extensionElements>
<zeebe:subscription correlationKey="=key" />
</bpmn:extensionElements>
</bpmn:message>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_16ue9kj">
<bpmndi:BPMNEdge id="Flow_0fwz2ln_di" bpmnElement="Flow_0fwz2ln">
<di:waypoint x="168" y="177" />
<di:waypoint x="200" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_199ps6v_di" bpmnElement="re_audit">
<di:waypoint x="1000" y="152" />
<di:waypoint x="1000" y="90" />
<di:waypoint x="390" y="90" />
<di:waypoint x="390" y="137" />
<bpmndi:BPMNLabel>
<dc:Bounds x="665" y="72" width="63" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0e397yl_di" bpmnElement="Flow_0e397yl">
<di:waypoint x="440" y="177" />
<di:waypoint x="475" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1lr5ur1_di" bpmnElement="Flow_1lr5ur1">
<di:waypoint x="300" y="177" />
<di:waypoint x="340" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0l4cgrv_di" bpmnElement="Flow_0l4cgrv">
<di:waypoint x="1190" y="177" />
<di:waypoint x="1272" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_177ps1y_di" bpmnElement="Flow_177ps1y">
<di:waypoint x="865" y="177" />
<di:waypoint x="892" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0c8hcd4_di" bpmnElement="Flow_0c8hcd4">
<di:waypoint x="790" y="177" />
<di:waypoint x="815" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_06la618_di" bpmnElement="Flow_06la618">
<di:waypoint x="665" y="177" />
<di:waypoint x="690" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1grwq17_di" bpmnElement="Flow_1grwq17">
<di:waypoint x="525" y="177" />
<di:waypoint x="552" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_11yboz4_di" bpmnElement="Flow_11yboz4">
<di:waypoint x="588" y="177" />
<di:waypoint x="615" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0wvvvjc_di" bpmnElement="Flow_0wvvvjc">
<di:waypoint x="928" y="177" />
<di:waypoint x="975" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0bqwngy_di" bpmnElement="Flow_0bqwngy">
<di:waypoint x="1025" y="177" />
<di:waypoint x="1090" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="132" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0d43kkb_di" bpmnElement="apply_auth">
<dc:Bounds x="200" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_05mf2rz_di" bpmnElement="confirm_by_supervisor">
<dc:Bounds x="892" y="159" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="865" y="121.5" width="89" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_001ghjo_di" bpmnElement="audit_by_reviewer">
<dc:Bounds x="552" y="159" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="527" y="202" width="86" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1mfbkhy_di" bpmnElement="Gateway_07ivcgb">
<dc:Bounds x="615" y="152" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1u2qqcb_di" bpmnElement="Gateway_1t2zx79">
<dc:Bounds x="475" y="152" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1be98t1_di" bpmnElement="notify_supervisor">
<dc:Bounds x="690" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1kgw3tw_di" bpmnElement="Gateway_00r4evy">
<dc:Bounds x="815" y="152" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1doq7r3_di" bpmnElement="audit">
<dc:Bounds x="340" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0jv5wxq_di" bpmnElement="Event_0jv5wxq">
<dc:Bounds x="1272" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0zsfe4z_di" bpmnElement="prove_auth">
<dc:Bounds x="1090" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0vfgvoa_di" bpmnElement="Gateway_1dmvvpa" isMarkerVisible="true">
<dc:Bounds x="975" y="152" width="50" height="50" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
from lib-bpmn-engine.
Got your point ... there seems to be another bug, working on it.
from lib-bpmn-engine.
Hi siconghuang,
I did just a minute ago post some fixes, which were still present, for the bug mentioned above.
I did some modifications as well, to make the code + BPMN more simple.
- IF there's just a single intermediate message catch event in the flow, you don't need a event gateway in front neither an exclusive gateway afterward
- you should not send events from inside a service task
- that might be technically possible, but is not necessary code, the engine does the process execution for you
- it's very difficult to read/understand such code
- from your example, I think it would be more convenient to have the #41 implemented, BUT as you can see, the "validate supervisor result" task takes care of updating the proper variable within the instance.
import (
"fmt"
"github.com/corbym/gocrest/is"
"github.com/corbym/gocrest/then"
"github.com/nitram509/lib-bpmn-engine/pkg/bpmn_engine"
"github.com/nitram509/lib-bpmn-engine/pkg/spec/BPMN20/process_instance"
"log"
"testing"
)
var (
bpmnEngine = bpmn_engine.New("auth_apply")
process *bpmn_engine.ProcessInfo
instance *bpmn_engine.ProcessInstanceInfo
)
// these test-* variables do mimic/simulate external events or manual actions
var testAuditAttempt = 0
const BEIJING = "Beijing"
const LONDON = "London"
func Test_apply_auth_loop(t *testing.T) {
// setup
var err error
process, err = bpmnEngine.LoadFromFile("auth_apply.bpmn")
bpmnEngine.AddTaskHandler("apply_auth", applyAuth)
bpmnEngine.AddTaskHandler("audit", audit)
bpmnEngine.AddTaskHandler("notify_supervisor", notifySupervisor)
bpmnEngine.AddTaskHandler("validate_supervisor_result", validateSupervisorResult)
bpmnEngine.AddTaskHandler("prove_auth", proveAuth)
instance, err = bpmnEngine.CreateInstance(process.ProcessKey, nil)
fmt.Println(err)
// HINT: BPMN spec allows message published with variables, but the engine does not (yet) support that
// so ideally, bpmnEngine.PublishEventForInstance() should support variables to pass in
bpmnEngine.RunOrContinueInstance(instance.GetInstanceKey())
then.AssertThat(t, instance.GetVariable("city"), is.EqualTo(LONDON))
bpmnEngine.PublishEventForInstance(instance.GetInstanceKey(), "supervision_reported") // first response from supervisor
bpmnEngine.RunOrContinueInstance(instance.GetInstanceKey()) // will loop back and executes the audit as well, stops because of second intermediate catch event
bpmnEngine.PublishEventForInstance(instance.GetInstanceKey(), "supervision_reported") // first response from supervisor
bpmnEngine.RunOrContinueInstance(instance.GetInstanceKey()) // will catch up the second event and exits the instance
then.AssertThat(t, instance.GetVariable("city"), is.EqualTo(BEIJING))
then.AssertThat(t, instance.GetState(), is.EqualTo(process_instance.COMPLETED))
}
func validateSupervisorResult(job bpmn_engine.ActivatedJob) {
reAudit := testAuditAttempt <= 1 // for testing purpose, the first attempt is always wrong
log.Println(fmt.Sprintf("validateSupervisorResult, reAudit=%t", reAudit))
job.SetVariable("reAudit", reAudit)
job.Complete()
}
func applyAuth(job bpmn_engine.ActivatedJob) {
log.Println("start to applyAuth")
job.Complete()
}
func audit(job bpmn_engine.ActivatedJob) {
log.Println("start to audit")
if testAuditAttempt < 1 {
job.SetVariable("city", LONDON)
} else {
job.SetVariable("city", BEIJING)
}
testAuditAttempt = testAuditAttempt + 1 // change audit result for next time
job.Complete()
}
func proveAuth(job bpmn_engine.ActivatedJob) {
log.Println("start to proveAuth")
job.Complete()
}
func notifySupervisor(job bpmn_engine.ActivatedJob) {
city := job.GetVariable("city").(string)
log.Println(fmt.Sprintf("start to notifySupervisor; city=%s", city))
job.Complete()
}
BPMN Source ...
auth_apply.bpmn.txt
from lib-bpmn-engine.
@lastchiliarch does my above proposal help?
from lib-bpmn-engine.
Related Issues (20)
- QUESTION: Process not in COMPLETED state? HOT 4
- Ensure that parallel flows are executed independently HOT 7
- QUESTION: How to execute a process in async way HOT 3
- Add support for link events HOT 3
- improve documentation: synchr. vs. asynch. jobs
- bpmn is driven through for temporal HOT 4
- Refactor using go-style Getters and Setters
- implement support for start timer event HOT 1
- evaluate expression using jsonlogic HOT 3
- Even more flexible task handlers HOT 9
- Can lib-bpmn-engine specify participants via assignment handler? HOT 1
- replace `MessageSubscription.originaActivity` with some smarter solution
- support assignee and candidateGroups for user tasks
- tried in private production project? HOT 1
- bpmnEngine.NewTaskHandler undefined HOT 6
- How to implement user task? HOT 3
- improve error handling handleIntermediateTimerCatchEvent() HOT 1
- improve test coverage
- Rework engine-name to be optional HOT 5
- fix data house keeping / cleanup
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from lib-bpmn-engine.