Skip to content

Commit 1a77ad2

Browse files
Tenant support (#246)
* add tenant configuration * Add tenant test, add some docs, activate ITests again * improve default itest configuration * Update spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/ProcessEngineWorker.kt Co-authored-by: i-am-not-giving-my-name-to-a-machine <4127235+i-am-not-giving-my-name-to-a-machine@users.noreply.github.com> --------- Co-authored-by: i-am-not-giving-my-name-to-a-machine <4127235+i-am-not-giving-my-name-to-a-machine@users.noreply.github.com>
1 parent 5a0ff86 commit 1a77ad2

12 files changed

Lines changed: 245 additions & 60 deletions

File tree

.github/workflows/master.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434

3535
# Build
3636
- name: Build with Maven
37-
run: ./mvnw clean verify -U -B -T4
37+
run: ./mvnw clean verify -U -B -T4 -ntp
3838

3939
# Get GPG private key into GPG
4040
- name: Import GPG Owner Trust

docs/process-engine-worker.md

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ API and allows to build universal workers.
99
First of all add the Process Engine Worker dependency to your projects classpath. In Maven add the following to your `pom.xml`:
1010

1111
```xml
12+
1213
<dependency>
1314
<groupId>dev.bpm-crafters.process-engine-worker</groupId>
1415
<artifactId>process-engine-worker-spring-boot-starter</artifactId>
@@ -19,6 +20,7 @@ First of all add the Process Engine Worker dependency to your projects classpath
1920
Now create a simple Spring component and annotate a method with a special annotation `@ProcessEngineWorker`:
2021

2122
```java
23+
2224
@Component
2325
@RequiredArgsConstructor
2426
public class MySmartWorker {
@@ -31,39 +33,40 @@ public class MySmartWorker {
3133
) {
3234
// execute some business code
3335
var fetched = fetchGoodsInPort.fetchGoods(order);
34-
36+
3537
return Map.of("shipped", fetched);
3638
}
3739
}
3840
```
3941

4042
The `@ProcessEngineWorker` annotation supports the following properties:
4143

42-
| Property | Type | Default | Description |
43-
|----------------|------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
44-
| `topic` | `String` | `""` | Topic name to subscribe this worker for. Alias for `value`. |
45-
| `autoComplete` | `boolean` | `true` | Flag indicating if the task should be automatically completed after the worker execution. If the return type is `Map<String, Any>`, it will overrule this setting and auto-complete with the returned payload. |
46-
| `completion` | `enum` | `DEFAULT` | Configures when the worker completes a task if `autoComplete` is active. Possible values are `DEFAULT`, `BEFORE_COMMIT`, and `AFTER_COMMIT`. Has no effect if the worker is not transactional. |
47-
| `lockDuration` | `long` | `-1` | Optional lock duration in milliseconds for this worker. If set to `-1` (default), the global configuration of the process engine adapter will be used. (Available since `0.8.0`) |
44+
| Property | Type | Default | Description |
45+
|----------------|-----------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
46+
| `topic` | `String` | `"__unset"` | Topic name to subscribe this worker for. Alias for `value`. |
47+
| `autoComplete` | `boolean` | `true` | Flag indicating if the task should be automatically completed after the worker execution. If the return type is `Map<String, Any>`, it will overrule this setting and auto-complete with the returned payload. |
48+
| `completion` | `enum` | `DEFAULT` | Configures when the worker completes a task if `autoComplete` is active. Possible values are `DEFAULT`, `BEFORE_COMMIT`, and `AFTER_COMMIT`. Has no effect if the worker is not transactional. |
49+
| `lockDuration` | `long` | `-1` | Optional lock duration in milliseconds for this worker. If set to `-1` (default), the global configuration of the process engine adapter will be used. (Available since `0.8.0`) |
50+
| `tenantId` | `String` | `""` | Optional tenant id to be used for current worker during the registration. Any non-blank value will be considered. If the value is empty, the value from the property will be used. |
4851

4952
## Method parameter resolution
5053

5154
Parameter resolution of the method annotated with `ProcessEngineWorker` is based on a set of strategies
5255
registered by the `ParameterResolver` bean. Currently, the following parameters are resolved:
5356

54-
| Type | Purpose |
55-
|----------------------------------------|---------------------------------------------------------------------------|
56-
| TaskInformation | Helper abstracting all information about the external task. |
57-
| ExternTaskCompletionApi | API for completing the external task manually |
58-
| VariableConverter | Special utility to read the process variable map and deliver typed value |
59-
| Map<String, Object> | Payload object containing all variables. |
60-
| Type annotated with @Variable("name") | Marker for a process variable. |
57+
| Type | Purpose |
58+
|---------------------------------------|--------------------------------------------------------------------------|
59+
| TaskInformation | Helper abstracting all information about the external task. |
60+
| ExternTaskCompletionApi | API for completing the external task manually |
61+
| VariableConverter | Special utility to read the process variable map and deliver typed value |
62+
| Map<String, Object> | Payload object containing all variables. |
63+
| Type annotated with @Variable("name") | Marker for a process variable. |
6164

6265
Usually, the requested variable is mandatory and the parameter resolver reports an error, if the requested variable is not
6366
available in the process payload. If you want to inject the variable only if it exists in the payload you have two options.
6467
Either you set the parameter `@Variable(name = "...", mandatory = false)` or you use `Optional<T>` instead of `T` as a variable
6568
type. If you are using Kotlin and don't like `Optional`, make sure to declare variable type as nullable (`T?` instead of `T`) and
66-
set the mandatory flag to `false`.
69+
set the mandatory flag to `false`.
6770

6871
## Method return type
6972

@@ -95,6 +98,7 @@ dev:
9598
worker:
9699
registerProcessWorkers: true # Enable or disable automatic worker registration
97100
completeTasksBeforeCommit: false # Determines whether tasks are completed before transaction commit
101+
tenantId: my-tenant # Specify the tenant ID for all workers, can be overridden by the value in the `@ProcessEngineWorker` annotation
98102
```
99103
100104

itest/spring-boot-starter-integration-test/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<phase>integration-test</phase>
5151
<goals>
5252
<goal>integration-test</goal>
53+
<goal>verify</goal>
5354
</goals>
5455
</execution>
5556
</executions>

itest/spring-boot-starter-integration-test/src/test/kotlin/dev/bpmcrafters/processengine/worker/fixture/InMemoryIdempotencyRegistryConfiguration.kt

Lines changed: 0 additions & 12 deletions
This file was deleted.

itest/spring-boot-starter-integration-test/src/test/kotlin/dev/bpmcrafters/processengine/worker/fixture/TestApplication.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import com.fasterxml.jackson.databind.ObjectMapper
44
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
55
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
66
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
7+
import dev.bpmcrafters.processengine.worker.idempotency.IdempotencyRegistry
8+
import dev.bpmcrafters.processengine.worker.idempotency.InMemoryIdempotencyRegistry
79
import org.springframework.boot.autoconfigure.SpringBootApplication
810
import org.springframework.boot.autoconfigure.domain.EntityScan
911
import org.springframework.context.annotation.Bean
12+
import org.springframework.context.annotation.Primary
13+
import org.springframework.context.annotation.Profile
1014
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
1115
import java.text.SimpleDateFormat
1216

@@ -21,4 +25,10 @@ class TestApplication {
2125
registerModule(JavaTimeModule())
2226
dateFormat = SimpleDateFormat("yyyy-MM-dd'T'hh:MM:ss.SSSz")
2327
}
28+
29+
@Bean
30+
@Primary
31+
@Profile("!jpa-idempotency")
32+
fun inMemIdempotencyRegistry(): IdempotencyRegistry = InMemoryIdempotencyRegistry()
33+
2434
}

itest/spring-boot-starter-integration-test/src/test/kotlin/dev/bpmcrafters/processengine/worker/itest/idempotency/IdempotencyITest.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package dev.bpmcrafters.processengine.worker.itest.idempotency
22

3-
import dev.bpmcrafters.processengine.worker.fixture.InMemoryIdempotencyRegistryConfiguration
43
import dev.bpmcrafters.processengine.worker.fixture.MyEntityRepository
54
import dev.bpmcrafters.processengine.worker.fixture.worker.WorkerWithTransactionalAnnotation
65
import dev.bpmcrafters.processengine.worker.fixture.worker.WorkerWithoutTransactionalAnnotation
@@ -22,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value
2221
import org.springframework.boot.autoconfigure.domain.EntityScan
2322
import org.springframework.context.annotation.Import
2423
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
24+
import org.springframework.test.context.ActiveProfiles
2525
import org.springframework.test.context.TestPropertySource
2626
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean
2727
import java.util.*
@@ -93,39 +93,42 @@ abstract class IdempotencyITest : FixtureITestBase() {
9393

9494
@Nested
9595
@Import(
96-
InMemoryIdempotencyRegistryConfiguration::class,
9796
WorkerWithoutTransactionalAnnotation::class
9897
)
9998
class InMemoryIdempotencyWithoutTransactionITest : IdempotencyITest()
10099

100+
@Nested
101101
@TestPropertySource(properties = ["dev.bpm-crafters.process-api.worker.complete-tasks-before-commit=false"])
102102
class InMemoryIdempotencyWithoutTransactionNotRemovingTaskResultITest : InMemoryIdempotencyWithoutTransactionITest()
103103

104104
@Nested
105105
@Import(
106-
InMemoryIdempotencyRegistryConfiguration::class,
107106
WorkerWithTransactionalAnnotation::class
108107
)
109108
class InMemoryIdempotencyWithTransactionITest : IdempotencyITest()
110109

110+
@Nested
111111
@TestPropertySource(properties = ["dev.bpm-crafters.process-api.worker.complete-tasks-before-commit=false"])
112112
class InMemoryIdempotencyWithTransactionNotRemovingTaskResultITest : InMemoryIdempotencyWithTransactionITest()
113113

114-
@Nested
115-
@Import(WorkerWithoutTransactionalAnnotation::class)
114+
@ActiveProfiles("jpa-idempotency")
116115
@EntityScan(basePackageClasses = [TaskLogEntry::class])
117116
@EnableJpaRepositories(basePackageClasses = [TaskLogEntryRepository::class])
118-
class JpaIdempotencyWithoutTransactionITest : IdempotencyITest()
117+
abstract class JpaIdempotencyITest : IdempotencyITest()
119118

119+
@Nested
120+
@Import(WorkerWithoutTransactionalAnnotation::class)
121+
class JpaIdempotencyWithoutTransactionITest : JpaIdempotencyITest()
122+
123+
@Nested
120124
@TestPropertySource(properties = ["dev.bpm-crafters.process-api.worker.complete-tasks-before-commit=false"])
121125
class JpaIdempotencyWithoutTransactionNotRemovingTaskResultITest : JpaIdempotencyWithoutTransactionITest()
122126

123127
@Nested
124128
@Import(WorkerWithTransactionalAnnotation::class)
125-
@EntityScan(basePackageClasses = [TaskLogEntry::class])
126-
@EnableJpaRepositories(basePackageClasses = [TaskLogEntryRepository::class])
127-
class JpaIdempotencyWithTransactionITest : IdempotencyITest()
129+
class JpaIdempotencyWithTransactionITest : JpaIdempotencyITest()
128130

131+
@Nested
129132
@TestPropertySource(properties = ["dev.bpm-crafters.process-api.worker.complete-tasks-before-commit=false"])
130133
class JpaIdempotencyWithTransactionNotRemovingTaskResulITest : JpaIdempotencyWithTransactionITest()
131134

spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/ProcessEngineWorker.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ annotation class ProcessEngineWorker(
1212
/**
1313
* Topic name to subscribe this worker for.
1414
*/
15-
@get: AliasFor(attribute = "value")
15+
@get:AliasFor(attribute = "value")
1616
val topic: String = DEFAULT_UNSET_TOPIC,
1717
/**
1818
* Topic name to subscribe this worker for.
1919
*/
20-
@get: AliasFor(attribute = "topic")
20+
@get:AliasFor(attribute = "topic")
2121
val value: String = DEFAULT_UNSET_TOPIC,
2222
/**
2323
* Flag, indicating if the task should be automatically completed after the execution of the worker.
@@ -38,17 +38,23 @@ annotation class ProcessEngineWorker(
3838
* If not specified (default: -1), the adapter's global configuration will be used.
3939
* @since 0.8.0
4040
*/
41-
val lockDuration: Long = DEFAULT_UNSET_LOCK_DURATION
41+
val lockDuration: Long = DEFAULT_UNSET_LOCK_DURATION,
42+
/**
43+
* Tenant ID to subscribe this worker for. Defaults to empty string representing no tenant.
44+
*/
45+
val tenantId: String = "",
4246
) {
4347
companion object {
4448
/**
4549
* Null value for the topic.
4650
*/
4751
const val DEFAULT_UNSET_TOPIC = "__unset"
52+
4853
/**
4954
* Sentinel value indicating lock duration is not specified.
5055
*/
5156
const val DEFAULT_UNSET_LOCK_DURATION = -1L
57+
5258
}
5359

5460
/**
@@ -59,12 +65,14 @@ annotation class ProcessEngineWorker(
5965
* Use default configured via property.
6066
*/
6167
DEFAULT,
68+
6269
/**
6370
* Execute external task completion before the transaction is committed.
6471
*/
6572
BEFORE_COMMIT,
73+
6674
/**
67-
* Execute external task completion after transaction is committed.
75+
* Execute external task completion after the transaction is committed.
6876
*/
6977
AFTER_COMMIT,
7078
}

spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/configuration/ProcessEngineWorkerProperties.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ data class ProcessEngineWorkerProperties(
2626
* if the completion of a task was successful but the removal of a task result was not.
2727
*/
2828
var removeTaskResultOnCompletion: Boolean = true,
29+
/**
30+
* Default tenant id to use for all workers.
31+
*/
32+
var tenantId: String? = null,
2933
) {
3034
companion object {
3135
const val DEFAULT_PREFIX = "dev.bpm-crafters.process-api.worker"

spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/registrar/ProcessEngineStarterRegistrar.kt

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,9 @@ import dev.bpmcrafters.processengine.worker.ProcessEngineWorker.Completion.DEFAU
88
import dev.bpmcrafters.processengine.worker.configuration.ProcessEngineWorkerAutoConfiguration
99
import dev.bpmcrafters.processengine.worker.configuration.ProcessEngineWorkerProperties
1010
import dev.bpmcrafters.processengine.worker.configuration.ProcessEngineWorkerProperties.Companion.DEFAULT_PREFIX
11-
import dev.bpmcrafters.processengineapi.task.CompleteTaskByErrorCmd
12-
import dev.bpmcrafters.processengineapi.task.CompleteTaskCmd
13-
import dev.bpmcrafters.processengineapi.task.FailTaskCmd
14-
import dev.bpmcrafters.processengineapi.task.ServiceTaskCompletionApi
15-
import dev.bpmcrafters.processengineapi.task.SubscribeForTaskCmd
16-
import dev.bpmcrafters.processengineapi.task.TaskInformation
17-
import dev.bpmcrafters.processengineapi.task.TaskSubscriptionApi
18-
import dev.bpmcrafters.processengineapi.task.TaskType
1911
import dev.bpmcrafters.processengine.worker.idempotency.IdempotencyRegistry
12+
import dev.bpmcrafters.processengineapi.CommonRestrictions
13+
import dev.bpmcrafters.processengineapi.task.*
2014
import io.github.oshai.kotlinlogging.KotlinLogging
2115
import org.springframework.beans.factory.config.BeanPostProcessor
2216
import org.springframework.boot.autoconfigure.AutoConfiguration
@@ -88,11 +82,16 @@ class ProcessEngineStarterRegistrar(
8882

8983
val completion = method.getCompletion()
9084
val customLockDuration = method.getLockDuration()
91-
val restrictions = if (customLockDuration == null) {
92-
mapOf()
93-
} else {
94-
mapOf("workerLockDurationInMilliseconds" to customLockDuration.toString()) // FIXME replace with constant introduced in Process Engine API 1.6
95-
}
85+
val tenantId = method.getTenantId() ?: processEngineWorkerProperties.tenantId
86+
87+
val restrictions: Map<String, String> = mutableMapOf<String, String>().apply {
88+
if (customLockDuration != null) {
89+
this[CommonRestrictions.WORKER_LOCK_DURATION_IN_MILLISECONDS] = customLockDuration.toString()
90+
}
91+
if (tenantId != null) {
92+
this[CommonRestrictions.TENANT_ID] = tenantId
93+
}
94+
}.toMap()
9695

9796
// check if the method or class is marked to run in transaction
9897
val isTransactional = method.isTransactional()

spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/registrar/ReflectionUtils.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,14 @@ fun Method.getTopic(): String {
152152
* Returns the auto-completion flag from annotation.
153153
*/
154154
fun Method.getAutoComplete(): Boolean {
155-
return this.getAnnotation(ProcessEngineWorker::class.java).autoComplete
155+
return AnnotationUtils.findAnnotation(this, ProcessEngineWorker::class.java)!!.autoComplete
156156
}
157157

158158
/**
159159
* Returns the auto-completion flag from annotation.
160160
*/
161161
fun Method.getCompletion(): Completion {
162-
return this.getAnnotation(ProcessEngineWorker::class.java).completion
162+
return AnnotationUtils.findAnnotation(this, ProcessEngineWorker::class.java)!!.completion
163163
}
164164

165165
/**
@@ -168,14 +168,26 @@ fun Method.getCompletion(): Completion {
168168
* @since 0.8.0
169169
*/
170170
fun Method.getLockDuration(): Long? {
171-
val lockDuration = this.getAnnotation(ProcessEngineWorker::class.java).lockDuration
171+
val lockDuration = AnnotationUtils.findAnnotation(this, ProcessEngineWorker::class.java)!!.lockDuration
172172
return if (lockDuration == ProcessEngineWorker.DEFAULT_UNSET_LOCK_DURATION) {
173173
null
174174
} else {
175175
lockDuration
176176
}
177177
}
178178

179+
/**
180+
* Returns the tenant id, configured on the annotation or null if it is not set.
181+
* @return tenant id, or null if the default should be used.
182+
* @since 0.8.4
183+
*/
184+
fun Method.getTenantId(): String? {
185+
val tenantId = AnnotationUtils.findAnnotation(this, ProcessEngineWorker::class.java)!!.tenantId
186+
return tenantId.ifBlank {
187+
null
188+
}
189+
}
190+
179191
/**
180192
* Checks if the method of the worker is transactional.
181193
* @return true, if the method should be executed transactional and be atomic with completion of the worker.

0 commit comments

Comments
 (0)