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

Open
#278 3 comments 0 reactions 1 assignee Claimed by @emaarco View on GitHub
Type: question
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.