Skip to content

As a user, I want to register interceptors for service task workers so I can add cross-cutting concerns without replacing the delivery layer #278

@emaarco

Description

@emaarco

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:

fun interface ServiceTaskInterceptor {
  fun intercept(context: ServiceTaskInterceptorContext, chain: ServiceTaskInterceptorChain)
}

data class ServiceTaskInterceptorContext(
  val taskInformation: TaskInformation,
  val payload: Map<String, Any?>,
  val taskDescriptionKey: String?,
  val taskType: TaskType
)

fun interface ServiceTaskInterceptorChain {
  fun proceed()
}

Example logging interceptor:

@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)

Metadata

Metadata

Assignees

Labels

Type: questionFurther information is requested

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions