Compensation Code

384 views
Skip to first unread message

Paolo Cabras

unread,
Oct 22, 2015, 6:04:23 AM10/22/15
to camunda BPM users
Hi everyone,

straight questions:

Is compensation covered by camunda java API?

If so, am I allowed to run compensation code if the activities to compensate are lo longer actives?

I've been trying to model my process in several ways but every time I reach that step I just get blocked.

Thanks,

Cheers,

Paolo

Daniel Meyer

unread,
Oct 22, 2015, 6:42:50 AM10/22/15
to camunda BPM users
Hi Paolo,

Camunda implements compensation as specified by BPMN.

Refer to the reference documentation:

Are there any specific issues you experience with that we can help you with?
If true, please post stripped down BPMN models and / or java code

Cheers,
daniel

Paolo Cabras

unread,
Oct 22, 2015, 8:14:03 AM10/22/15
to camunda BPM users
Hi Daniel,

thanks for fast replying.

Here you have the diagram


The process has 2 main scenarios:
-mutation successful mutation
-mutation aborted

The mutation can be aborted from the subprocess Cancel New Mutation Process by changing the value of the process variable 
MUTATION_FLOW which is checked at the xor gateway.
IMPORTANT: if the user task Take in Charge has completed the process cannot be aborted any longer!!!

A listener to that variable should "reset" the process through the ProcessInstanceModificationBuilder.
The thing is that I would like to "roll back" changes made in activities before the Take in Charge activity.

I thought that using ProcessInstanceModificationBuilder and cancelling activities already reached  (createProcessInstanceModification().cancelActivityInstance()) , I would have executed the compensation code.
That doesn't work because they are no longer active so no longer available which means

org.camunda.bpm.engine.exception.NullValueException: activityInstance is null
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at org.camunda.bpm.engine.impl.util.EnsureUtil.generateException(EnsureUtil.java:283)
at org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull(EnsureUtil.java:44)
at org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull(EnsureUtil.java:39)
at org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull(EnsureUtil.java:31)
at org.camunda.bpm.engine.impl.cmd.AbstractProcessInstanceModificationCommand.getScopeExecutionForActivityInstance(AbstractProcessInstanceModificationCommand.java:103)
at org.camunda.bpm.engine.impl.cmd.ActivityInstanceCancellationCmd.determineSourceInstanceExecution(ActivityInstanceCancellationCmd.java:49)
at org.camunda.bpm.engine.impl.cmd.AbstractInstanceCancellationCmd.execute(AbstractInstanceCancellationCmd.java:33)
at org.camunda.bpm.engine.impl.cmd.AbstractInstanceCancellationCmd.execute(AbstractInstanceCancellationCmd.java:26)
at org.camunda.bpm.engine.impl.cmd.ModifyProcessInstanceCmd.execute(ModifyProcessInstanceCmd.java:52)
at org.camunda.bpm.engine.impl.cmd.ModifyProcessInstanceCmd.execute(ModifyProcessInstanceCmd.java:30)
at org.camunda.bpm.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:24)
at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:97)
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:32)
at org.camunda.bpm.engine.impl.ProcessInstanceModificationBuilderImpl.execute(ProcessInstanceModificationBuilderImpl.java:219)
at org.camunda.bpm.engine.impl.ProcessInstanceModificationBuilderImpl.execute(ProcessInstanceModificationBuilderImpl.java:210)
at be.fgov.mobilit.hrm.camunda.CamundaAPITest.testCompensationOnSubmitNewRequest(CamundaAPITest.java:219)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)

It makes sense of course but now I wonder how I should model/implement this behavior.

If I have well understood for using cancellation I need a transaction but I think is not my case.
For having a transaction I need to include take in charge task but it lives in a different lane.
If I left that task out of the transaction, I should rollback a successful transaction which, as far as I know, is quite against transaction definition.

Any hint/idea? 

Cheers,

Paolo

Philipp Ossler

unread,
Oct 22, 2015, 10:06:06 AM10/22/15
to camunda BPM users
Hi Paolo,

I updated your process definition [1]. I found multiple problems:
- a compensation handler (task with property "isForCompensation") should always attach to a compensation boundary event
- use intermediate throw compensation event instead of process instance modification

The bound of the "transaction" is defined by the subprocess. The compensation can only be triggered while execute the subprocess because it is triggered from an event subprocess inside the process. 
Please have a look at [2] for details about compensation events.

Greetings,
Philipp

Paolo Cabras

unread,
Oct 22, 2015, 10:35:15 AM10/22/15
to camunda BPM users
Hi Philipp,

thanks for the idea.
At event level seems to be what I was looking for but there are things that I think doesn't match with 
my requirement.

I start by saying that I'm not a bpmn expert so I'm maybe wrong.
The sub process end of mutation is now a sub process of the sub process "build mutation" (I hope you understand what I mean) which is not the case.
When the process reaches the task Complete-and-submit-mutation steps quickly towards the task Take-in-charge-mutation 
(those are service tasks so no realistic time for interacting with them) and I wouldn't be able to trigger the Cancel mutation sub process if I wanted.
The Take-in-charge-mutation acts a bit as a "waiting point" too.
In other words users should be able to abort the process till someone take in charge the mutation.

Am I wrong with the analysis?

Thanks for helping,
Cheers,

Paolo

Philipp Ossler

unread,
Oct 23, 2015, 3:38:14 AM10/23/15
to camunda BPM users
Hi Paolo,

you are right. I see two ways to achieve your goal:
* span the subprocess over two lanes and include the user task "Take in charge mutation". It is possible since camunda ignoring lanes while execution.
* attach a (message) boundary on the user task "Take in charge mutation" and throw the compensation event when it is triggered. Maybe you can then remove the sub-process and event-subprocess if the service tasks can / should not be interrupted. 

Greetings,
Philipp

Paolo Cabras

unread,
Oct 23, 2015, 3:38:17 AM10/23/15
to camunda BPM users
Hi everyone,

I'll try to be a bit more exhaustive.


 I wouldn't be able to trigger the Cancel mutation sub process if I wanted.

The cancel mutation sub process should be triggered by users who live in the first lane.
What I model is a user accessing a web page and aborting a mutation by clicking on a button.
I guess is more like throwing a signal than sending a message.
The signal should be caught by a receiver (listener or whatever). The take-in-charge task is active so I guess a listener can handle that.
What is also very important is that the sub process cancel mutation can be triggered just during the lifecycle of the take-in-charge task!!!
That's also why the model you proposed I think doesn't match my requirement.

Now the solution I found easier to me is a listener on the assignee change event.
A disposer assignee who represent the aborting path in the main process.
Also the one aborting the process would change the value of the process variable in order to force the process towards the aborting path (xor gateway).
So, to sum up, I'm trying to implement a listener to the change change assignee event which deals with the process reset as described above.

This scenario however doesn't handle the "rollback" step.
That's why I was thinking about compensation.

The only thing I found acceptable is implementing a few service task in the abort path (after the xor gateway) for dealing with that.
But I'm not sure is a proper model in terms of BPMN.

Thank for any past or further help,
Cheers,

Paolo

Philipp Ossler

unread,
Oct 23, 2015, 3:56:48 AM10/23/15
to camunda BPM users
Hi Paolo,

I recommend a signal boundary event [1] on the user task "take in charge" instead of a listener. The behavior is like you described for listener. When the user task is active then the mutation can be canceled by the signal.
When the signal is triggered then you should throw the compensation event. While compensation, the compensation handlers , like "remove new mutation data", are triggered.

If I understand you right then you want to end the process instance after received the cancel signal and undo the mutation stuff. Instead of the xor gateway, you achieve the goal with the throw compensation end which is attached to the user task if the boundary event is interrupting. So when the signal is received then the process instance left the user task, start compensation and end after compensation is completed.

Just one more thing, maybe you want to use a message instead of a signal because a signal is received by all process instances. So if you have two instances of the mutation process then both will maybe aborted. [2]

Greetings,
Philipp

Paolo Cabras

unread,
Oct 23, 2015, 3:57:38 AM10/23/15
to camunda BPM users
Hi Philipp,

the modeler seems not to ignore the difference between lanes.
I can't extend the subprocess to a task in different lane even if that would be the best solution for me:I would declare that subprocess as a transaction!!
Once I reach the take-in-charge the sub process is no longer active so no way of reversing it.

Paolo Cabras

unread,
Oct 23, 2015, 4:01:40 AM10/23/15
to camunda BPM users
Hi Philipp,

apparently every time I answer, you are doing the same so I see your tips later :).
I'll have a look at the solution you proposed and I'll post the result.

Cheers, 
Paolo

Paolo Cabras

unread,
Oct 23, 2015, 9:08:04 AM10/23/15
to camunda BPM users
Hi Phillip,

actually that was really helpful!!

It just behave like I wanted.
Now, just for completeness, for throwing the compensation code I give this instruction to the engine inside the Message Event ExecutionListener:

delegateExecution.getProcessEngineServices().getRuntimeService().
        createProcessInstanceModification
(delegateExecution.getProcessInstanceId()).
        startBeforeActivity
("EndEvent_CancelledMutation").execute();

Is that the proper way?
If so, how can I prove that the process has gracefully shutdown?

ProcessInstance#isEnded() lead to assertionError and also if I query for active ProcessInstances I got the one supposed to be killed active.

Anyway really thanks!!!

Cheers,

Paolo

Philipp Ossler

unread,
Oct 23, 2015, 9:41:02 AM10/23/15
to camunda BPM users
Hi Paolo,

you should not use process instance modification. It is designed for repair and migrate process instance [1]. If you attached the message boundary event to the user task then you just call:
runtimeService.correlateMessage("cancel-mutation");

See [2] for details. If you prefer a signal event then see [3]. 

When the message / signal event is triggered then the user task is canceled and the correlation event is thrown. After all compensation handler are executed successfully then the throw compensation event is left and the process instance ends (gracefully). 

The compensation event should look like:
<endEvent id="compensateMutation">
  <compensateEventDefinition />
</endEvent>

See [4] for details.

Greetings,

Philipp



Paolo Cabras

unread,
Oct 26, 2015, 3:38:22 AM10/26/15
to camunda BPM users
Hi Philipp,

sorry for not posting back.
Actually I tried your advice and it worked as you said.
Now I did some "undo" on my .bpmn and tests are failing and I'm trying to find the problem.
I'll review your tips and hopefully I'll find the trick.

Thanks once again,
Cheers,

Paolo
Reply all
Reply to author
Forward
0 new messages