bpm-crafters / bpm-crafters/process-engine-api
As a user, I want to register interceptors for service task workers so I can add cross-cutting concerns without replacing the delivery layer
- Dominant language
- Kotlin
- Stars
- 21
- Forks
- 5
- Avg merge
- 6d 6h
- Merged PRs (30d)
- 3
Description
### Scenario
I want to add cross-cutting concerns (logging, APM/OpenTelemetry tracing, auditing)
around every service task worker execution without having to replace the entire `ExternalServiceTaskDelivery`
bean or rely on Spring AOP (which cannot easily access `TaskInformation` and variables).
The library supports two registration paths:
1. **Annotation-based** (`@ProcessEngineWorker`) via `process-engine-worker`
2. **Manual** — directly calling `TaskSubscriptionApi.subscribeForTask(SubscribeForTaskCmd(..., action: TaskHandler))`
A solution must cover **both paths**.
### Current Behaviour
There is no first-class interceptor concept. To intercept worker execution, users must either:
- Replace the entire `ExternalServiceTaskDelivery` bean (large surface area, error-prone, engine-specific)
- Use Spring AOP (hard or impossible to access `TaskInformation` + variables + topic name)
- Implement a custom `ParameterResolutionStrategy` or `ResultResolutionStrategy` (worker-lib only, no lifecycle hooks)
### Wanted Behaviour
A `ServiceTaskInterceptor` interface that allows users to register one or more Spring beans to be applied
around every service task execution — regardless of whether the subscription was created via
`@ProcessEngineWorker` or via a manual `subscribeForTask()` call.
Proposed API:
```kotlin
fun interface ServiceTaskInterceptor {
fun intercept(context: ServiceTaskInterceptorContext, chain: ServiceTaskInterceptorChain)
}
data class ServiceTaskInterceptorContext(
val taskInformation: TaskInformation,
val payload: Map,
val taskDescriptionKey: String?,
val taskType: TaskType
)
fun interface ServiceTaskInterceptorChain {
fun proceed()
}
```
Example logging interceptor:
```kotlin
@Component
class LoggingServiceTaskInterceptor : ServiceTaskInterceptor {
private val log = LoggerFactory.getLogger(this::class.java)
override fun intercept(ctx: ServiceTaskInterceptorContext, chain: ServiceTaskInterceptorChain) {
log.info("Executing [{}] with {} variables", ctx.taskDescriptionKey, ctx.payload.size)
chain.proceed()
log.info("Completed [{}]", ctx.taskDesclingKey)
}
}
```
The right interception point is inside `AbstractTaskSubscriptionApiImpl.subscribeForTask()` — wrapping the
`action` TaskHandler before storing the `TaskSubscriptionHandle`. This makes it transparent to all delivery
implementations (pull or push) and covers both registration paths automatically. Interceptors are ordered
via `@Order`/`Ordered`. When no interceptors are registered, behaviour is identical to today (zero overhead).
### Possible Workarounds
- Provide a custom `ExternalServiceTaskDelivery` bean (engine-specific, complex, replaces entire delivery layer)
- Spring AOP on `@ProcessEngineWorker` methods (does not work for manual `TaskHandler` registrations; `TaskInformation` and variables are not directly accessible as method parameters in all cases)
- Custom `ParameterResolutionStrategy` (worker-lib only, no pre/post hooks around the actual execution)
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.