Use BPMN Model API for typical compensation use case


#1

I want to define the “classical” compensation example using the Model API

Now I struggle how to define this in the Model Api, I would start like this - but how can I define the CancelCar activity which is not embedded in the normal flow?

  .addModelInstance("travel.bpmn", Bpmn.createProcess("travel").executable() //
    .startEvent()
    .serviceTask().name("Reserve car") //.camundaClass(ReserveCar.class)
      .boundaryEvent().compensateEventDefinition().activityRef("compensateCar").compensateEventDefinitionDone()
    .done()

I also thinking about creating an issue or PR to improve the API in a way to allow something like this - as I think this is far more intuitive for the use case at hand. Or what do you think?

  .addModelInstance("travel.bpmn", Bpmn.createProcess("travel").executable() //
    .startEvent()
    .serviceTask().name("Reserve car") //.camundaClass(ReserveCar.class)
      .boundaryEvent().compensateEventDefinition().serviceTask().name("Cancel car")
    .done()

Cheers
Bernd


#2

I think you can’t do that with the fluent builder at the moment. This is how we do it in the engine test suite:

public static void addUserTaskCompensationHandler(BpmnModelInstance modelInstance, String boundaryEventId, String compensationHandlerId) {

    BoundaryEvent boundaryEvent = modelInstance.getModelElementById(boundaryEventId);
    BaseElement scope = (BaseElement) boundaryEvent.getParentElement();

    UserTask compensationHandler = modelInstance.newInstance(UserTask.class);
    compensationHandler.setId(compensationHandlerId);
    compensationHandler.setForCompensation(true);
    scope.addChildElement(compensationHandler);

    Association association = modelInstance.newInstance(Association.class);
    association.setAssociationDirection(AssociationDirection.One);
    association.setSource(boundaryEvent);
    association.setTarget(compensationHandler);
    scope.addChildElement(association);
}

I agree that having this as part of the fluent builder would be nice. Feel free to make a proposal and raise a pull request. I would probably go with something like:

Bpmn.createProcess("travel").executable() //
    .startEvent()
    .serviceTask().name("Reserve car") //.camundaClass(ReserveCar.class)
        .compensationHandler()
          .serviceTask().name("Cancel car")
        .compensationHandlerDone()
    .done()

edit: corrected the code snippet


#3

Hi Thorben.
Thanks a lot!

What do you think about a solution using a static helper like mock or assertion frameworks are typically do it?

Bpmn.createProcess("travel").executable() //
    .startEvent()
    .serviceTask().name("Reserve car").camundaClass(ReserveCar.class)
        .compensationHandler(
               Bpmn.serviceTask().name("Cancel car"))
    .done()

But it might be a new concept not used anywhere else, but I think it is more intuitive than compensationHandlerDone (I think also at other places, but thats too late anyway ;-)).


#4

Hm, good question, not sure what the best way is. I personally lean towards consistency in such questions, but that’s debatable. If we want to prevent users from creating invalid models (e.g. compensation handlers with outgoing sequence flows), the compensationHandler() approach might be favourable, as it could return a builder that is limited to creating a single task.

On a side note, I found the idea proposed by @David_Karr quite interesting:

Java 8 is out of the picture at the moment, though.


#5

Note that it’s possible to build APIs that can be enhanced with lambdas without requiring Java 8. Parameters that represent instances of SAM classes can use anonymous inner classes in Java 7, but true lambdas in Java 8.


#6

Sorry, what’s a SAM class? :slight_smile:


#7

I agree on the argument that expressing a hierarchical structure in a linear way is kind of hard to understand. But I definitely doubt that anonymous inner classes are more readable. Where is the problem with using static helper methods where the result can be passed to other methods?

The compensationHandler( someCompensationTask ) approach indeed makes it impossible to attach more than one activity as compensation action - so you cannot use that in a wrong way. It is also very readable. You could also do sub processes in a ciomparable way:

Bpmn.createProcess("travel").executable() //
    .startEvent()
    .subProcess(
        Bpmn.startEvent().serviceTask().endEvenent())
    ....

I personally like that much more, as it is very intuitive - and with static imports it gets also very readable. If we decide that this direction make sense, we could introduce this step by step for different use cases, very much as we did for fluent service API’s.

WDYT?

Cheers
Bernd


#8

I quickly tried to squeeze an quick win into the current code base but surrendered. I think we should even consider adding a new fluent builder focusing on the real-life use cases making them as easy as possible for NON CAMUNDA or BPMN experts. I added it to the roadmap discussions.

For the time being I added an own SagaBuilder now hiding the glitches of the Model API: https://github.com/flowing/flowing-trip-booking-saga/blob/master/src/main/java/io/flowing/trip/saga/camunda/SagaBuilder.java

Now at least my visible flow definition looks like I want it to be:

SagaBuilder saga = SagaBuilder.newSaga("trip") //
        .activity("Reserve car", ReserveCarAdapter.class) //
        .compensationActivity("Cancel car", CancelCarAdapter.class) //
        .activity("Book hotel", BookHotelAdapter.class) //
        .compensationActivity("Cancel hotel", CancelHotelAdapter.class) //
        .activity("Book flight", BookFlightAdapter.class) //
        .compensationActivity("Cancel flight", CancelFlightAdapter.class) //
        .triggerCompensationOnAnyError() //
        .done();//

  camunda.getRepositoryService().createDeployment() //
        .addModelInstance("trip.bpmn", saga.getModel()) //
        .deploy();

#9

Thanks for sharing. What about a Java-8-based community extension for a “proper” fluent API?


#11

Having 7.8.0-alpha1 (https://blog.camunda.org/post/2017/06/camunda-bpm-780-alpha1-released/) the situation got great by the way :slight_smile:
I kept my small DSL: https://github.com/flowing/flowing-trip-booking-saga/blob/master/src/main/java/io/flowing/trip/saga/camunda/TripBookingSaga.java
But the implemenation is just translating that into model API - very lean: https://github.com/flowing/flowing-trip-booking-saga/blob/master/src/main/java/io/flowing/trip/saga/camunda/builder/SagaBuilder.java

Love it…