feat(embedded): introduce EngineCommandExecutor for pluggable command dispatch#157
feat(embedded): introduce EngineCommandExecutor for pluggable command dispatch#157zambrovski merged 1 commit intodevelopfrom
Conversation
a17ea0b to
23f67d9
Compare
Introduce EngineCommandExecutor to centralise how the four core engine calls (correlateMessage, sendSignal, startProcess, deploy) are dispatched. The default executor submits work to ForkJoinPool.commonPool(), mirroring the behaviour of a remote engine and keeping adapters portable across engine implementations (embedded ↔ remote, Camunda 7 ↔ Zeebe). Users override the Spring bean with any java.util.concurrent.Executor to change the dispatch strategy — most commonly a same-thread executor for @transactional coupling. Closes #156
23f67d9 to
3e61ede
Compare
| class EngineCommandExecutor( | ||
| private val executor: Executor = ForkJoinPool.commonPool() | ||
| ) { |
There was a problem hiding this comment.
I wonder, would it be sufficient to not have an EngineCommandExecutor, and just inject a "same thread executor" bean where needed and pass it to the supplyAsync calls there? Should be the same effect but with one fewer class.
There was a problem hiding this comment.
@i-am-not-giving-my-name-to-a-machine - if i understand you right, this should work as well, yes. And was also my first suggestion (to just execute the operation sync, and then wrap the result in a CompletableFuture.completedFuture(...) or as an alternative use a sync executor right away)
However, @zambrovski's take on this to make it customizable. So that the default behaviour is still async, and you need to opt in to sync execution with a "same thread executor". So that you do not become coupled to the embedded engine through implicit behavior of the adapter—but rather the application remains portable.
Thus i'd suggested this solution (which would also open the door to set another pool than the default forkJoinPool of supplyAsync, if a user would for example like to use something like newVirtualThreadPerTaskExecutor for whatever reason)
There was a problem hiding this comment.
I'm with @zambrovski on this, that it should be customizable and the default is what the API implies: async. However, I really like the option for transactional engine commands. I'd merely introduce an Executor bean (that is specifically for the service implementations that you modified). All I'm suggesting is to replace
@Bean
@ConditionalOnMissingBean
fun engineCommandExecutor(): EngineCommandExecutor = EngineCommandExecutor()with
@Bean
@ConditionalOnMissingBean
fun engineCommandExecutor(): Executor = ForkJoinPool.commonPool()and delete EngineCommandExecutor. Unless this would replace something in Spring and it would really hurt everyone who uses this lib, because this change should not interfere with anything else of course.
There was a problem hiding this comment.
ah, got it :) not sure about that either, whether there is some kind of default bean on the Executor class.
Therefore i implemented it as a property of another class.
To make it
- a) more explicit what the bean is about (just relevant to sending commands to the engine)
- b) prevent issues if a user/spring itself already defined another
Executorbean, thats used somewhere else.
There was a problem hiding this comment.
Well, if an indirection is necessary, so be it.
Tbh, Spring beans are not my strong suit, my background is in Jakarta EE and way back stuff, but I'd expect a mechanism in Spring that enables arbitrary beans of the same type that don't interfere with any default beans.
There was a problem hiding this comment.
If you use beans with specific qualifiers/names throughout the whole adapter, I expect this should be possible in Spring without bigger problems.
But given that, I don't see an outweighting advantage in this approach, since i force the user to give the bean a specific name - and neither a big disadvantage in defining one class that represents a lot better what the executor is about. Think it shouldn’t be that expressive to maintain in the long run - also considering that it could be somehow shared since all embedded adapters could require it
But i can live with both
|
I think the implementation is good enough with both approaches, but since @emaarco provided the PR, let his implementation remain there until we find a sound reason why the indirection is bad and a default executor should be exposed as a Spring Bean. |
Overview
nullwhen no processDefinition was found. Neither was a processInstance created thenFurther steps