AnsweredAssumed Answered

Discussion: calling a service from inside a JavaDelegate

Question asked by jbarrez on Sep 25, 2012
Latest reply on Sep 27, 2012 by jbarrez
Hi guys,

One thing that is a 'trend' on the forum, is problems sprouting from the fact people try to use one of the services inside their JavaDelegate. The current implementation will simply create a whole new command stack (and new connection/transaction), which leads to strange problems. We've been saying to those people that this is wrong usage … but I can't blame them for assuming it. And so I want to fix this.

I'm talking about the following usage:


public class StartProcessInstanceTestDelegate implements JavaDelegate {

  public void execute(DelegateExecution execution) throws Exception {
    …
    runtimeService.startProcessInstanceByKey("someProcess");
    …
  }

}

This can currently go wrong in many ways. For example, what if the process crashes further on, then the process started inside this delegate will have been created (because of the new transaction).

The fix for this issue is actually pretty simple, also because the groundwork in the code was already 99% ready for it: when a new CommandContext is created, it is pushed onto a (threadlocal) stack, and at the end it is popped off again. The solution for our problem here, is to check this stack when a 'nested command' (which is what the service invocation is translated to) is executed. When a context is found on the stack, reuse it. This way, the current transaction is used, and all is how you'd expect it from Activiti and how it handles transactions. The exception is of course the 'txRequiresNew' command stack, which would never want to reuse the context.

I implemented the changes needed in https://github.com/Activiti/Activiti/commit/eba608b2d9023b2a7847e7d1ba819983ca6da882. All tests and Spring test run with these changes. I do need some help from the cdi guys.

You will see I added two test cases in that commit:
* CallServiceInServiceTaskTest.testStartProcessFromDelegate : if you check the logs here, you'll see that the connection is being reused. In the old implementation, you see 2 separate connections.
* CallServiceInServiceTaskTest.testRollBackOnException : this is the interesting one. Here, the behavior will be wrong with the old implementation: one process has an exception, but the other one is still created. In the new implementation, both are rolled back (as you'd expect it).

What do you guys think about this? Do you see any pitfalls I overlooked?

Secondly, you'll see in the test JavaDelegate that I got the runtimeService by calling


RuntimeService runtimeService = Context.getProcessEngineConfiguration().getRuntimeService();

However, the Context object is an impl object and I'm not thinking about exposing it. As I see it, we can offer two ways to fetch the services:

* Add an API class 'DelegateHelper' which does the call above behind the scenes
* Expose the services on the DelegateExecution

The second approach feels a bit strange to me, but then again people will find it easier….

WDYT?

Outcomes