This project contains a modified version of camunda-engine
that is more Clojure friendly.
lib/camunda-engine-7.15.0-SNAPSHOT.jar
based on the following commit: https://github.com/arnaudgeiser/camunda-bpm-platform/commit/3e0c618b9fc1cbcb59e971753e97297f07b5b2d9resources/simple-diagram.bpmn
contains a new XML attribute calledclojure
src/clj-camunda/core.clj
contains the integration and examples incomment
This is my answer to Tim Zöller about his talk Workflow engines with Clojure, it's a match.
During his presentation, he draw a clear picture about the experience we currently have when
working with Camunda from Clojure. It's accurate, straightforward and the exploration he did
is beneficial for the community.
Here I propose to look at the situation from another angle:
What is the experience we want to have when working with Camunda from Clojure?
Let's start with Tim's conclusion:
I agree on most of them, with different thumb directions.
I confess, delegate is more readable in Clojure than Java, but
not by a large margin. Having to generate a class for every service task
is impractical. But he knows it pretty well:
We need to implement the same Java interface. This is not ideal, I know that. And this also lead to us... needing to precompile this class to work with the REPL
Having to work with Java Interop is not that uncomfortable. What bother me the most is the number of lines I have to write for every square in my BPMN diagram (and the fact that every class needs to be precompiled).
Taking an even simpler diagram with 3 service tasks and one gateway, I would expect to write the following either through Java Interop or through a Clojure macro:
Through Java Interop
(defn send-mail [e]
(let [email (get (.getVariables e) "email")]
(prn "I'm sending an email to : " email)
(re-find #"gmail" email)))
(defn log-other-email-account [_]
(prn "Log other email account"))
(defn log-gmail-account [_]
(prn "Log gmail account"))
Through Clojure macro
By having a defprocess
macro we would be able to provide an idiomatic feeling when
working with Camunda
(defmacro defprocess
"Defines a Camunda process"
[& clauses]
(cons 'do
(for [clause clauses
:let [[n p f] clause]]
`(defn ~n ~'[e]
~(if (seq p)
`(let [~@p (execution-entity->map ~'e)] ~f)
f)))))
(defprocess
(send-mail [{{:keys [email]} :variables}]
(prn (str "I'm sending an email to " email))
(re-find #"gmail" email))
(log-other-email-account [] (prn "Log other email account"))
(log-gmail-account [] (prn "Log gmail account")))
This experience won't be available out of the box without a small contribution in Camunda. It would be great if Camunda was able to call Clojure code. It can be done with some minor modifications:
Java Interop is not really fun, but by providing a Clojure wrapper around Camunda, it shouldn't be a big deal.
As mentioned, Task Orchestration is pretty neat already. Having a keyword based API would be more convenient
In theory, Camunda should be able to support a large variety of languages. Every JSR-233 ScriptEngine should be usable. In practice, it's not the case. Clojure should be considered a dynamic language, but based on the following code, it was not true:
public static boolean isDynamicScriptExpression(String language, String value) {
return StringUtil.isExpression(value) &&
!JuelScriptEngineFactory.names.contains(language.toLowerCase());
}
Only languages using ${}
are considered dynamic:
public static boolean isExpression(String text) {
text = text.trim();
return text.startsWith("${") || text.startsWith("#{");
}
A better support for dynamic scripting engine is necessary. A dirty hack:
- Support
clojure
orclojure-var
attribute in Camunda Engine (https://github.com/arnaudgeiser/camunda-bpm-platform/commit/3e0c618b9fc1cbcb59e971753e97297f07b5b2d9 ) - Support
clojure
orclojure-var
attribute in Camunda Modeler - Better ScriptEngine support in Camunda
- Clojure wrapper around Camunda for better experience
- Some processes are simple and could be express directly from EDN files