@nanobpm/nano-ide-app-embedded-graalvm-native 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/nano-ide.ext.json +2 -1
- package/package.json +1 -1
- package/templates/embedded-graalvm-starter/README.md +5 -6
- package/templates/embedded-graalvm-starter/microservice/pom.xml +3 -3
- package/templates/embedded-graalvm-starter/microservice/src/main/java/com/example/KycMicroservice.java +21 -15
- package/templates/embedded-graalvm-starter/microservice/src/main/java/com/example/OuterKycService.java +52 -47
package/nano-ide.ext.json
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"templates": [
|
|
15
15
|
{
|
|
16
16
|
"id": "embedded-graalvm-starter",
|
|
17
|
-
"label": "KYC microservice (GraalVM native)
|
|
17
|
+
"label": "KYC microservice (GraalVM native)",
|
|
18
|
+
"description": "Same demo, ~30 MB standalone binary (ADR 0005)"
|
|
18
19
|
}
|
|
19
20
|
],
|
|
20
21
|
"toolchain": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-ide-app-embedded-graalvm-native",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "GraalVM Native Image variant of the embedded Nano JVM template: same Bernd-in-JVM code, compiled to a single standalone binary (~30 MB, no JDK required at runtime).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -31,8 +31,8 @@ and run.
|
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
33
|
./microservice/target/kyc-microservice
|
|
34
|
-
# defaults to
|
|
35
|
-
|
|
34
|
+
# defaults to http://localhost:8080 (Camunda REST); override:
|
|
35
|
+
CAMUNDA_REST_ADDRESS=http://gateway.example.com:8080 ./microservice/target/kyc-microservice
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## What's inside the binary
|
|
@@ -43,15 +43,14 @@ FALCON_URL=ws://gateway.example.com/falcon ./microservice/target/kyc-microservic
|
|
|
43
43
|
- The Nano engine's wasm blob shipped inside `nano-bernd` (kept by
|
|
44
44
|
`-H:IncludeResources=nano-bernd/.*`)
|
|
45
45
|
- Chicory (pure-Java wasm interpreter — AOT-compiles cleanly)
|
|
46
|
-
-
|
|
46
|
+
- Stock `io.camunda:camunda-client-java` for the outer REST job worker
|
|
47
47
|
|
|
48
48
|
## Reflection config
|
|
49
49
|
|
|
50
50
|
`microservice/src/main/resources/META-INF/native-image/reflect-config.json`
|
|
51
51
|
flags the `EmbeddedEngine`, `ActivatedJob`, and `WasmManifest` types
|
|
52
|
-
because `
|
|
53
|
-
|
|
54
|
-
so the methods aren't stripped.
|
|
52
|
+
because `nano-bernd` uses reflection to talk to them — Graal's
|
|
53
|
+
closed-world compilation needs this so the methods aren't stripped.
|
|
55
54
|
|
|
56
55
|
## Notes
|
|
57
56
|
|
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
<maven.compiler.source>21</maven.compiler.source>
|
|
23
23
|
<maven.compiler.target>21</maven.compiler.target>
|
|
24
24
|
|
|
25
|
-
<camunda-client.version>
|
|
25
|
+
<camunda-client.version>8.9.11</camunda-client.version>
|
|
26
26
|
<nano-bernd.version>0.2.0</nano-bernd.version>
|
|
27
27
|
<native.maven.plugin.version>0.10.3</native.maven.plugin.version>
|
|
28
28
|
</properties>
|
|
29
29
|
|
|
30
30
|
<dependencies>
|
|
31
31
|
<dependency>
|
|
32
|
-
<groupId>io.
|
|
33
|
-
<artifactId>camunda-client-java
|
|
32
|
+
<groupId>io.camunda</groupId>
|
|
33
|
+
<artifactId>camunda-client-java</artifactId>
|
|
34
34
|
<version>${camunda-client.version}</version>
|
|
35
35
|
</dependency>
|
|
36
36
|
<dependency>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* KYC Microservice — embedded-Nano demo (ADR 0005).
|
|
3
3
|
*
|
|
4
|
-
* [
|
|
5
|
-
* │
|
|
4
|
+
* [ Outer BPMN engine ] [ This JVM process ]
|
|
5
|
+
* │ │
|
|
6
6
|
* models/onboarding.bpmn ┌─ OuterKycService ─┐
|
|
7
|
-
* (deployed
|
|
7
|
+
* (deployed to the engine) │ Camunda REST │
|
|
8
8
|
* │ │ verify_kyc worker │
|
|
9
|
-
* │
|
|
10
|
-
* │◄──────────────── verify_kyc
|
|
11
|
-
* │ │ │
|
|
12
|
-
* │ │ ▼
|
|
9
|
+
* │ stock Camunda REST (activateJobs) │ │
|
|
10
|
+
* │◄──────────────── verify_kyc ─────────┤ │
|
|
11
|
+
* │ │ │ │
|
|
12
|
+
* │ │ ▼ │
|
|
13
13
|
* │ │ ┌─ InnerKycService ─┐
|
|
14
14
|
* │ │ │ Bernd embedded │
|
|
15
15
|
* │ │ │ engine + kyc.bpmn │
|
|
@@ -23,10 +23,14 @@
|
|
|
23
23
|
* │ │ └───────────────────┘
|
|
24
24
|
* │◄──── completeJob(decision) ──────────┤
|
|
25
25
|
*
|
|
26
|
+
* The outer engine can be Camunda 8, Camunda Self-Managed, or a Nano
|
|
27
|
+
* gateway — we only speak stock Camunda REST on the outer path.
|
|
28
|
+
*
|
|
26
29
|
* Two bounded contexts, two files:
|
|
27
|
-
* - OuterKycService owns the
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* - OuterKycService owns the REST job worker wire-up. Onboarding team's
|
|
31
|
+
* boundary.
|
|
32
|
+
* - InnerKycService owns the embedded engine + inner flow. Compliance
|
|
33
|
+
* team's boundary. Trivially extractable into its own process.
|
|
30
34
|
*
|
|
31
35
|
* This class only wires them together and keeps the JVM alive.
|
|
32
36
|
*/
|
|
@@ -37,15 +41,17 @@ import java.util.concurrent.CountDownLatch;
|
|
|
37
41
|
|
|
38
42
|
public final class KycMicroservice {
|
|
39
43
|
|
|
40
|
-
// Set
|
|
41
|
-
|
|
44
|
+
// Set CAMUNDA_REST_ADDRESS to override; defaults to the Nano IDE gateway's
|
|
45
|
+
// REST endpoint. A stock Camunda 8 gateway on :8080 also works (same REST
|
|
46
|
+
// API).
|
|
47
|
+
private static final String DEFAULT_REST_ADDRESS = "http://localhost:8080";
|
|
42
48
|
|
|
43
49
|
public static void main(final String[] args) throws Exception {
|
|
44
|
-
final URI
|
|
45
|
-
System.getenv().getOrDefault("
|
|
50
|
+
final URI restAddress = URI.create(
|
|
51
|
+
System.getenv().getOrDefault("CAMUNDA_REST_ADDRESS", DEFAULT_REST_ADDRESS));
|
|
46
52
|
|
|
47
53
|
final InnerKycService inner = InnerKycService.boot();
|
|
48
|
-
final OuterKycService outer = OuterKycService.boot(
|
|
54
|
+
final OuterKycService outer = OuterKycService.boot(restAddress, inner);
|
|
49
55
|
|
|
50
56
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
51
57
|
outer.close();
|
|
@@ -1,70 +1,78 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Outer KYC service — wires the
|
|
3
|
-
*
|
|
2
|
+
* Outer KYC service — wires the outer engine's `verify_kyc` job type to the
|
|
3
|
+
* InnerKycService over stock Camunda REST.
|
|
4
4
|
*
|
|
5
|
-
* Bounded context: onboarding owns the outer
|
|
6
|
-
* onboarding.bpmn
|
|
5
|
+
* Bounded context: onboarding owns the outer engine (the deployed
|
|
6
|
+
* onboarding.bpmn; this is the service task that satisfies it). This class
|
|
7
7
|
* knows nothing about how the KYC decision is produced — it just delegates
|
|
8
8
|
* to InnerKycService.verify(customerId) and completes the outer job with the
|
|
9
9
|
* returned decision.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The outer engine can be Camunda 8, Camunda Self-Managed, or a Nano
|
|
12
|
+
* gateway — we only speak stock Camunda REST here. If InnerKycService moved
|
|
13
|
+
* out to its own JVM/process, the only thing that changes here is the field
|
|
14
|
+
* type + how it's injected — the verify call site stays intact.
|
|
14
15
|
*
|
|
15
|
-
* Concurrency:
|
|
16
|
-
* driver-bound; handling multiple concurrent outer verify_kyc
|
|
17
|
-
* would let inner activateJobs polls race across KycContexts.
|
|
18
|
-
* horizontally with more microservice instances.
|
|
16
|
+
* Concurrency: maxJobsActive=1 (serial). The InnerKycService's embedded
|
|
17
|
+
* engine is driver-bound; handling multiple concurrent outer verify_kyc
|
|
18
|
+
* activations would let inner activateJobs polls race across KycContexts.
|
|
19
|
+
* Scale horizontally with more microservice instances.
|
|
19
20
|
*/
|
|
20
21
|
package com.example;
|
|
21
22
|
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
23
|
+
import io.camunda.client.CamundaClient;
|
|
24
|
+
import io.camunda.client.api.response.ActivatedJob;
|
|
25
|
+
import io.camunda.client.api.worker.JobWorker;
|
|
25
26
|
import java.net.URI;
|
|
27
|
+
import java.time.Duration;
|
|
26
28
|
import java.util.Map;
|
|
27
|
-
import java.util.concurrent.TimeUnit;
|
|
28
29
|
|
|
29
30
|
public final class OuterKycService implements AutoCloseable {
|
|
30
31
|
|
|
31
32
|
private static final String OUTER_JOB_TYPE = "verify_kyc";
|
|
32
33
|
private static final String OUTER_WORKER = "kyc-microservice";
|
|
33
|
-
private static final
|
|
34
|
-
private static final
|
|
34
|
+
private static final Duration JOB_LEASE = Duration.ofSeconds(30);
|
|
35
|
+
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30);
|
|
35
36
|
|
|
36
|
-
private final
|
|
37
|
+
private final CamundaClient client;
|
|
37
38
|
private final InnerKycService inner;
|
|
39
|
+
private JobWorker worker;
|
|
38
40
|
|
|
39
|
-
private OuterKycService(final
|
|
40
|
-
this.
|
|
41
|
+
private OuterKycService(final CamundaClient client, final InnerKycService inner) {
|
|
42
|
+
this.client = client;
|
|
41
43
|
this.inner = inner;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
|
-
* Open a
|
|
46
|
-
* verify_kyc. Delegates every activation to {@code inner}.
|
|
47
|
+
* Open a stock Camunda REST connection to the outer engine and subscribe
|
|
48
|
+
* to verify_kyc. Delegates every activation to {@code inner}.
|
|
47
49
|
*/
|
|
48
|
-
public static OuterKycService boot(final URI
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
public static OuterKycService boot(final URI restAddress, final InnerKycService inner) {
|
|
51
|
+
final CamundaClient client = CamundaClient.newClientBuilder()
|
|
52
|
+
.restAddress(restAddress)
|
|
53
|
+
.preferRestOverGrpc(true)
|
|
54
|
+
.defaultRequestTimeout(REQUEST_TIMEOUT)
|
|
55
|
+
.build();
|
|
56
|
+
System.out.println("[outer] connected to onboarding engine at " + restAddress);
|
|
53
57
|
|
|
54
|
-
final OuterKycService service = new OuterKycService(
|
|
55
|
-
|
|
56
|
-
OUTER_JOB_TYPE
|
|
57
|
-
|
|
58
|
+
final OuterKycService service = new OuterKycService(client, inner);
|
|
59
|
+
service.worker = client.newWorker()
|
|
60
|
+
.jobType(OUTER_JOB_TYPE)
|
|
61
|
+
.handler((jobClient, job) -> service.handleVerifyKyc(job))
|
|
62
|
+
.name(OUTER_WORKER)
|
|
63
|
+
.maxJobsActive(1)
|
|
64
|
+
.timeout(JOB_LEASE)
|
|
65
|
+
.open();
|
|
58
66
|
System.out.println(
|
|
59
67
|
"[outer] subscribed to " + OUTER_JOB_TYPE
|
|
60
|
-
+ " — start an onboarding instance
|
|
68
|
+
+ " — start an onboarding instance");
|
|
61
69
|
return service;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
private void handleVerifyKyc(final
|
|
65
|
-
final
|
|
66
|
-
final
|
|
67
|
-
final String customerId = vars.
|
|
72
|
+
private void handleVerifyKyc(final ActivatedJob job) {
|
|
73
|
+
final long outerJobKey = job.getKey();
|
|
74
|
+
final Map<String, Object> vars = job.getVariablesAsMap();
|
|
75
|
+
final String customerId = String.valueOf(vars.getOrDefault("customerId", "cust-unknown"));
|
|
68
76
|
|
|
69
77
|
System.out.println();
|
|
70
78
|
System.out.println("═══════════════════════════════════════════════════════════════");
|
|
@@ -76,7 +84,7 @@ public final class OuterKycService implements AutoCloseable {
|
|
|
76
84
|
try {
|
|
77
85
|
decision = inner.verify(customerId);
|
|
78
86
|
} catch (final Exception e) {
|
|
79
|
-
// Do NOT complete the outer job — let
|
|
87
|
+
// Do NOT complete the outer job — let the engine's job timeout drive a retry.
|
|
80
88
|
System.err.println("[inner] flow failed for customer=" + customerId + ": " + e);
|
|
81
89
|
e.printStackTrace(System.err);
|
|
82
90
|
System.err.println("[outer] NOT completing " + OUTER_JOB_TYPE
|
|
@@ -88,12 +96,10 @@ public final class OuterKycService implements AutoCloseable {
|
|
|
88
96
|
System.out.println("═══════════════════════════════════════════════════════════════");
|
|
89
97
|
|
|
90
98
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.completeJob(outerJobKey, Map.of("decision", decision, "customerId", customerId))
|
|
96
|
-
.get(CALL_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
99
|
+
client.newCompleteCommand(job)
|
|
100
|
+
.variables(Map.of("decision", decision, "customerId", customerId))
|
|
101
|
+
.send()
|
|
102
|
+
.join();
|
|
97
103
|
} catch (final Exception e) {
|
|
98
104
|
System.err.println("[outer] complete failed: " + e.getMessage());
|
|
99
105
|
}
|
|
@@ -101,10 +107,9 @@ public final class OuterKycService implements AutoCloseable {
|
|
|
101
107
|
|
|
102
108
|
@Override
|
|
103
109
|
public void close() {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} catch (final Exception ignored) {
|
|
107
|
-
// best effort
|
|
110
|
+
if (worker != null) {
|
|
111
|
+
try { worker.close(); } catch (final Exception ignored) { /* best effort */ }
|
|
108
112
|
}
|
|
113
|
+
try { client.close(); } catch (final Exception ignored) { /* best effort */ }
|
|
109
114
|
}
|
|
110
115
|
}
|