Groovy script reuse

I have done quite a bit of digging to find the best way to reuse groovy scripts on the BPMN. Both on the forum as well as in the Camunda documentation. As well as experimentation with Camunda.

This seems like a simple problem but it has been surprisingly difficult to solve.

As an example use-case, I would like to execute the same groovy script on a service task that I execute on a Signal Event start task, to recalculate some business logic. Or I may want that exact same groovy script business logic re-calculation to occur on two different service tasks on two different areas of the BPMN.

I have found one solution based on Scripting | docs.camunda.org is to set a output parameter sourceCode with type Text and put a normal multi-line groovy script as the text. This can then be referenced on another task as ${sourceCode}. While this works, it has a 4,000 character limit due to database column constraint and one of the scripts we use is about 4,200 characters long.

I’ve heard of an approach using spin from How to handle large process variables, but have not been able to get an approach like this to work with a normal multi-line groovy script. The premise attempted was to store the script as a script of:

execution.setVariable("var", S('{"sourceCode":"full 
mult-line 
script 
here"}'))

Then later reference this as:

execution.getVariable("var").prop('sourceCode')

This gets around the 4,000 character limit, but seems not to work due to natural line breaks in a normal groovy script and increasingly feels like a hack.

I am currently using the approach where a groovy script is used as an External Resource using classpath:// and using .groovy extension files that are stored in src/main/resources and deployed with the jar file. This works and is the best solution I have found so far, but comes with a concern that has both a pro and a con: the concern being that multiple process definitions can be impacted by the external groovy script file change. This could be a good thing if a small bug fix were made in the groovy script. But in the case that major new non backward-compatible script changes were needed, this would risk impacting previously deployed process definitions. In our prod environment, we have potentially long-running user tasks, so we can conceivably have a case where some old process definitions still have running instances. We have not had the need yet to migrate these.

We are using Camunda 7 in a SpringBoot configuration and are currently deploying BPMNs through the standard built in Camunda annotation @EnableProcessApplication with no special processes.xml configuration.

I have seen mention of deploying script with BPMN with either REST API or Java code, but it sounds like that would require us to refactor our deployment process and there is no documentation I can find on this. I’m assuming this means the script would stay with the deployment and be referenced with the deployment:// prefix.

Surely I am missing something basic and there is a simple way to reuse groovy script code in Camunda?

Hi @madmax25

I know this does not answer your question, but do you have a special reason for wanting to use scripting tasks? Your process application runs inside a Spring application - so why not use Java delegates? This way you can create resuable Java components and you don’t have to deal with all the script source problems.

Just a thought.

BR
Michael

Java Delegate would come with the same problem as currently employed solution of external script task as a classpath resource:

I am currently using the approach where a groovy script is used as an External Resource using classpath:// and using .groovy extension files that are stored in src/main/resources and deployed with the jar file. This works and is the best solution I have found so far, but comes with a concern that has both a pro and a con: the concern being that multiple process definitions can be impacted by the external groovy script file change. This could be a good thing if a small bug fix were made in the groovy script. But in the case that major new non backward-compatible script changes were needed, this would risk impacting previously deployed process definitions. In our prod environment, we have potentially long-running user tasks, so we can conceivably have a case where some old process definitions still have running instances. We have not had the need yet to migrate these.

So if we have a Java bean as service and a method such as recalculate to recalculate business logic, we might have to employ a versioning scheme such as a recalculateV2 for backward incompatible changes.

I found a working solution for source code variable over 4,000 characters using Map. This can optionally be setup in the same script task that the variable is used.

I created a Script Task with Input Parameter as Type Script and Script Format groovy and Script Type Inline Script. I set the script to:

def testSourceCode = '''
import org.slf4j.Logger
import org.slf4j.LoggerFactory

final Logger logger = LoggerFactory.getLogger("my-camunda-script-logger")

logger.info("01- Add some comment that will make this go over the character limit.")
logger.info("02- Add some comment that will make this go over the character limit.")
logger.info("03- Add some comment that will make this go over the character limit.")
logger.info("04- Add some comment that will make this go over the character limit.")
logger.info("05- Add some comment that will make this go over the character limit.")
logger.info("06- Add some comment that will make this go over the character limit.")
logger.info("07- Add some comment that will make this go over the character limit.")
logger.info("08- Add some comment that will make this go over the character limit.")
logger.info("09- Add some comment that will make this go over the character limit.")
logger.info("10- Add some comment that will make this go over the character limit.")
logger.info("11- Add some comment that will make this go over the character limit.")
logger.info("12- Add some comment that will make this go over the character limit.")
logger.info("13- Add some comment that will make this go over the character limit.")
logger.info("14- Add some comment that will make this go over the character limit.")
logger.info("15- Add some comment that will make this go over the character limit.")
logger.info("16- Add some comment that will make this go over the character limit.")
logger.info("17- Add some comment that will make this go over the character limit.")
logger.info("18- Add some comment that will make this go over the character limit.")
logger.info("19- Add some comment that will make this go over the character limit.")
logger.info("20- Add some comment that will make this go over the character limit.")
logger.info("21- Add some comment that will make this go over the character limit.")
logger.info("22- Add some comment that will make this go over the character limit.")
logger.info("23- Add some comment that will make this go over the character limit.")
logger.info("24- Add some comment that will make this go over the character limit.")
logger.info("25- Add some comment that will make this go over the character limit.")
logger.info("26- Add some comment that will make this go over the character limit.")
logger.info("27- Add some comment that will make this go over the character limit.")
logger.info("28- Add some comment that will make this go over the character limit.")
logger.info("29- Add some comment that will make this go over the character limit.")
logger.info("30- Add some comment that will make this go over the character limit.")
logger.info("31- Add some comment that will make this go over the character limit.")
logger.info("32- Add some comment that will make this go over the character limit.")
logger.info("33- Add some comment that will make this go over the character limit.")
logger.info("34- Add some comment that will make this go over the character limit.")
logger.info("35- Add some comment that will make this go over the character limit.")
logger.info("36- Add some comment that will make this go over the character limit.")
logger.info("37- Add some comment that will make this go over the character limit.")
logger.info("38- Add some comment that will make this go over the character limit.")
logger.info("39- Add some comment that will make this go over the character limit.")
logger.info("40- Add some comment that will make this go over the character limit.")
logger.info("41- Add some comment that will make this go over the character limit.")
logger.info("42- Add some comment that will make this go over the character limit.")
logger.info("43- Add some comment that will make this go over the character limit.")
logger.info("44- Add some comment that will make this go over the character limit.")
logger.info("45- Add some comment that will make this go over the character limit.")
logger.info("46- Add some comment that will make this go over the character limit.")
logger.info("47- Add some comment that will make this go over the character limit.")
logger.info("48- Add some comment that will make this go over the character limit.")
logger.info("49- Add some comment that will make this go over the character limit.")
logger.info("50- Add some comment that will make this go over the character limit.")
'''

def sourceCodeMap = [example: testSourceCode]

execution.setVariable("sourceCodeMap", sourceCodeMap)

This is used on the same Script Task on the General tab with Script Format groovy Script Type Inline Script and Script:

${ execution.getVariable("sourceCodeMap").example }

This is presumably working because the value in the Map is Object and is getting stored as BLOB avoiding the varchar(4000) limitation of string.

The advantage being that the source code stays with the BPMN definition.

cc: @Kikol

Hi @madmax25

Yes, I understand. You want a way to make sure that your service task code follows the process instance to avoid any backwards compatibility issues. So for long running processes the original code runs with the process instance.

I come to remember the JBoss jBPM project. That was a process engine project that had support for that particular use case. The byte stream of the Java classes that were used for task code, was actually saved to DB as a part of the process instance.

The downside of jBPM project was when one had to fix a critical bug in the running process instances and had to change the code and save a new version of the Java class :slight_smile:

This is off course also something you would have to take in to consideration.

Glad you got it working.

BR
Michael

Conclusion –

Option 1: Store source code in BPMN Text

Concerns:

  • 4,000 character source code limit
  • Source code is mutable, exposing this approach to potential bugs or security issues

Option 2: Store source code in BPMN Map

Benefits:

  • Avoids 4,000 character source code limit

Concerns:

  • Source code exposed anywhere the Camunda variables are exposed, i.e. Camunda Cockpit, any APIs that return process variables, etc.
  • Seems like a non-conventional approach (read: hack) and may be vulnerable to future Camunda upgrades breaking this approach
  • Source code is mutable, exposing this approach to potential bugs or security issues

Option 3: Store source code in external script referenced by classpath

Benefits:

  • If multiple process definitions are using the same groovy script and a fix is needed, the fix would apply to all of the process definitions

Concerns:

  • The concern of backward incompatible changes for multiple process definitions referencing the same groovy script.

Work around:

  • Version the external groovy script files. Process definition will reference classpath://groovy/businessLogicV1.groovy for some certain business logic. If a future change is needed that is backward compatible, it will be added to businessLogicV1.groovy. If it is not backward compatible, the new process definition will use new script businessLogicV2.groovy. That way older process definitions can continue to use businessLogicV1.groovy while the new ones switch to using businessLogicV2.groovy.

We are going to pursue Option 3 sans any new ideas, concerns, or challenges.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.