Fallback on other path in BPMN when no retries are left

I want to do retrying on a service task, but use a fallback path whenever the retries do not lead to a successful result. A naive modeling would be e.g. this:

grafik

I could write an incident handler to be called at the right moment, when all retries are done. But I cannot send a message like the modeles one - as the engine has not yet entered the node (because auf asyncBefore=true).

Any spontaneous ideas how to implement such a behavior?

Another route I could envision is to code an own behavior and then check for the retry count of the job. If 0 a BPMN-error is raised instead of just throwing an exception from the attached delegate. What do you think? Then it would look like this:

grafik

Thanks!
Bernd

Hi @BerndRuecker

maybe you can wrap that in a subprocess?

process

regards,
Dominik

1 Like

I like the idea with the BPMN error, as it uses the error event for handling an error condition in the process. You could transparently hook in your custom behavior by implementing a parse listener that wraps and replaces the actual activity behavior and catches any exception, deciding if it should be propagated or become a BPMN error (under the assumption that the engine has no dirty instanceof tricks somewhere :slight_smile: ). Then it works with all types of service task implementations.

Yeah - cool idea -that fits what I had in mind. Do you know spontaneously if and how I can easily retrieve the currently executed Job from within a behavior?

Hi Dominik.

Yeah - possible - but then the model is much harder to read or change - so I would not like to go down this route.

But thanks for the suggestion :slight_smile:

Cheers
Bernd

You can access it from internal API via org.camunda.bpm.engine.impl.context.Context.getJobExecutorContext().getCurrentJob(). Note that #getJobExecutorContext returns null when the code is not executed by the job executor.

Awesome - thanks a lot. That helps me further. I can add a note here once I have some example code.

Just used this code - works like a charm:

public class GuardedServiceA implements JavaDelegate {

  private static final String NO_RETRIES_ERROR = "NO_RETRIES";
  
  public static boolean fail = false;
  public static int countFailed = 0;
  public static int countSuccess = 0;

  @Override
  public void execute(DelegateExecution ctx) throws Exception {
    JobExecutorContext jobExecutorContext = Context.getJobExecutorContext();
    if (jobExecutorContext!=null && jobExecutorContext.getCurrentJob()!=null) {
      // this is called from a Job
      if (jobExecutorContext.getCurrentJob().getRetries()<=1) {
        // and the job will run out of retries when it fails again
        try {
          doExecute(ctx);          
        } catch (Exception ex) {
          // Probably save the exception somewhere
          throw new BpmnError(NO_RETRIES_ERROR);
        }
        return;
      }      
    }
    // otherwise normal behavior (including retries possibly)
    doExecute(ctx);    
  }

  private void doExecute(DelegateExecution ctx) {
    if (fail) {
      countFailed++;
      throw new RuntimeException("ServiceA fails as expected");
    }
    countSuccess++;    
  }

}
5 Likes

Hi @BerndRuecker

This code is really useful. Is this put on a start listener delegate or directly inside the service delegate?

Hi,

Here’s a classless reference implementation which may be of interest…

regards

Rob

Thanks @Webcyberrob, So, if I’m understanding I have to do it for every delegate service task, and it cannot be done in a listener.

It has to be on the delegate yes. Depending on the environment you might use some tricks to make it more generic though, see e.g. this example where I used an annotation:

and weaved in the logic using aspects:

Not that I think that’s the best approach - but there are ways to make this more generic if you fancy it :slight_smile:

1 Like