@5minds/processcube_app_sdk 8.2.3-develop-17340a-mlmezagc → 8.2.3-develop-d7a750-mnizx1s1
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/README.md +350 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,9 +48,192 @@ import { DynamicLink } from '@5minds/processcube_app_sdk/client';
|
|
|
48
48
|
|
|
49
49
|
### External Tasks
|
|
50
50
|
|
|
51
|
-
External Tasks ermöglichen es,
|
|
51
|
+
External Tasks ermöglichen es, eigene Geschäftslogik in einer Next.js App auszuführen, die von der ProcessCube Engine als Aufgabe vergeben wird. Erreicht ein BPMN-Prozess einen External Service Task, veröffentlicht die Engine diesen unter einem **Topic**. Ein passender Worker in der App holt sich den Task ab, verarbeitet ihn und gibt das Ergebnis zurück.
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Das App SDK übernimmt dabei die komplette Infrastruktur: Worker-Prozesse werden automatisch gestartet, überwacht und bei Fehlern neu gestartet. Der Entwickler schreibt nur die eigentliche Handler-Funktion.
|
|
54
|
+
|
|
55
|
+
#### Architektur
|
|
56
|
+
|
|
57
|
+
Das folgende Diagramm zeigt die drei beteiligten Schichten und ihre Kommunikation:
|
|
58
|
+
|
|
59
|
+
```mermaid
|
|
60
|
+
graph LR
|
|
61
|
+
subgraph ProcessCube Engine
|
|
62
|
+
E[Engine]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
subgraph Next.js App – Hauptprozess
|
|
66
|
+
A[ExternalTaskAdapter]
|
|
67
|
+
W[File Watcher]
|
|
68
|
+
T[Token Management]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
subgraph Eigener Node.js Prozess je Topic
|
|
72
|
+
WP1[Worker Process<br/>Topic: order/process]
|
|
73
|
+
WP2[Worker Process<br/>Topic: invoice/send]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
E -- "HTTP: Fetch & Lock /<br/>Finish / Error /<br/>Extend Lock" --> WP1
|
|
77
|
+
E -- "HTTP: Fetch & Lock /<br/>Finish / Error /<br/>Extend Lock" --> WP2
|
|
78
|
+
|
|
79
|
+
A -- "IPC: create / restart /<br/>updateIdentity" --> WP1
|
|
80
|
+
A -- "IPC: create / restart /<br/>updateIdentity" --> WP2
|
|
81
|
+
|
|
82
|
+
W -- "Dateiänderung erkannt" --> A
|
|
83
|
+
T -- "Token-Refresh" --> A
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Hauptkomponenten:**
|
|
87
|
+
|
|
88
|
+
| Komponente | Aufgabe |
|
|
89
|
+
|---|---|
|
|
90
|
+
| **ExternalTaskAdapter** | Läuft im Hauptprozess der Next.js App. Überwacht das Dateisystem, startet Worker-Prozesse, verwaltet Tokens und koordiniert Restarts. |
|
|
91
|
+
| **ExternalTaskWorkerProcess** | Eigenständiger Node.js-Kindprozess (einer pro Topic). Lädt den transpilierten Handler, verbindet sich per HTTP-Long-Polling mit der Engine und verarbeitet Tasks. |
|
|
92
|
+
| **ProcessCube Engine** | Verwaltet BPMN-Prozesse und vergibt External Tasks an Worker über das Fetch-and-Lock-Protokoll. |
|
|
93
|
+
|
|
94
|
+
#### Lebenszyklus eines External Tasks
|
|
95
|
+
|
|
96
|
+
Das folgende Sequenzdiagramm zeigt, wie ein External Task von der Engine zum Worker gelangt, verarbeitet und abgeschlossen wird:
|
|
97
|
+
|
|
98
|
+
```mermaid
|
|
99
|
+
sequenceDiagram
|
|
100
|
+
participant Engine as ProcessCube Engine
|
|
101
|
+
participant Worker as Worker Process
|
|
102
|
+
|
|
103
|
+
loop Polling-Schleife
|
|
104
|
+
Worker->>Engine: POST /external_tasks/fetch_and_lock<br/>(workerId, topic, maxTasks, lockDuration)
|
|
105
|
+
Note right of Engine: Long-Polling — Anfrage wartet<br/>bis Task verfügbar oder Timeout
|
|
106
|
+
Engine-->>Worker: ExternalTask[] (mit Payload)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
Note over Worker: Handler-Funktion wird aufgerufen:<br/>handler(payload, task, signal)
|
|
110
|
+
|
|
111
|
+
loop Lock-Verlängerung
|
|
112
|
+
Worker->>Engine: PUT /external_tasks/{id}/extend_lock<br/>(additionalDuration)
|
|
113
|
+
Note right of Engine: Lock wird verlängert,<br/>damit der Task nicht abläuft
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
alt Erfolg
|
|
117
|
+
Worker->>Engine: PUT /external_tasks/{id}/finish<br/>(result)
|
|
118
|
+
Note right of Engine: Ergebnis wird als<br/>Prozessvariable gespeichert
|
|
119
|
+
else Fehler
|
|
120
|
+
Worker->>Engine: PUT /external_tasks/{id}/error<br/>(errorCode, errorMessage)
|
|
121
|
+
Note right of Engine: Fehler wird im<br/>Prozess behandelt
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Ablauf im Detail:**
|
|
126
|
+
|
|
127
|
+
1. Der Worker pollt die Engine per HTTP-Long-Polling nach neuen Tasks für sein Topic.
|
|
128
|
+
2. Sobald ein Task verfügbar ist, sperrt die Engine ihn (Lock) und liefert ihn mit dem Payload aus.
|
|
129
|
+
3. Der Worker ruft die Handler-Funktion auf und übergibt Payload, Task-Metadaten und ein AbortSignal.
|
|
130
|
+
4. Während der Verarbeitung verlängert der Worker automatisch den Lock, damit die Engine den Task nicht vorzeitig freigibt.
|
|
131
|
+
5. Nach Abschluss meldet der Worker das Ergebnis (Finish) oder einen Fehler (Error) an die Engine.
|
|
132
|
+
|
|
133
|
+
#### Worker-Startup und IPC-Kommunikation
|
|
134
|
+
|
|
135
|
+
Das App SDK startet pro `external_task.ts`-Datei einen eigenen Node.js-Kindprozess. Die Kommunikation zwischen Hauptprozess (Adapter) und Kindprozess (Worker) läuft über IPC (Inter-Process Communication):
|
|
136
|
+
|
|
137
|
+
```mermaid
|
|
138
|
+
sequenceDiagram
|
|
139
|
+
participant FS as Dateisystem
|
|
140
|
+
participant Adapter as ExternalTaskAdapter<br/>(Hauptprozess)
|
|
141
|
+
participant Worker as Worker Process<br/>(Kindprozess)
|
|
142
|
+
participant Engine as ProcessCube Engine
|
|
143
|
+
|
|
144
|
+
FS->>Adapter: Neue Datei erkannt:<br/>app/order/process/external_task.ts
|
|
145
|
+
|
|
146
|
+
Adapter->>Adapter: Datei transpilieren (esbuild)
|
|
147
|
+
Adapter->>Adapter: Topic ableiten: order/process
|
|
148
|
+
|
|
149
|
+
Adapter->>Worker: fork() — neuer Node.js-Prozess
|
|
150
|
+
|
|
151
|
+
Adapter->>Worker: IPC: { action: "create",<br/>topic, identity, moduleString }
|
|
152
|
+
|
|
153
|
+
Worker->>Worker: Handler-Modul aus String laden
|
|
154
|
+
Worker->>Engine: Polling starten (Fetch & Lock)
|
|
155
|
+
Note over Worker,Engine: Worker verarbeitet Tasks…
|
|
156
|
+
|
|
157
|
+
Note over FS: Datei wird geändert
|
|
158
|
+
|
|
159
|
+
FS->>Adapter: Dateiänderung erkannt
|
|
160
|
+
|
|
161
|
+
Adapter->>Adapter: Datei neu transpilieren
|
|
162
|
+
Adapter->>Worker: IPC: { action: "restart",<br/>topic, identity, moduleString }
|
|
163
|
+
|
|
164
|
+
Worker->>Worker: Alten Worker stoppen
|
|
165
|
+
Worker->>Worker: Neuen Handler laden
|
|
166
|
+
Worker->>Engine: Polling neu starten
|
|
167
|
+
|
|
168
|
+
Note over Adapter: Token läuft ab (85% Lebensdauer)
|
|
169
|
+
|
|
170
|
+
Adapter->>Adapter: Neuen Token holen (OpenID Connect)
|
|
171
|
+
Adapter->>Worker: IPC: { action: "updateIdentity",<br/>identity }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**IPC-Nachrichten:**
|
|
175
|
+
|
|
176
|
+
| Action | Richtung | Beschreibung |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `create` | Adapter → Worker | Initialer Start: Übergibt Topic, Identity und transpilierten Handler-Code |
|
|
179
|
+
| `restart` | Adapter → Worker | Hot-Reload: Stoppt den alten Worker und startet mit neuem Code (gleiche Worker-ID) |
|
|
180
|
+
| `updateIdentity` | Adapter → Worker | Aktualisiert den Auth-Token auf dem laufenden Worker |
|
|
181
|
+
|
|
182
|
+
#### Fehlerbehandlung und Restart-Strategie
|
|
183
|
+
|
|
184
|
+
Das System hat zwei Ebenen der Fehlerbehandlung: im Worker-Prozess selbst und im Adapter (Hauptprozess).
|
|
185
|
+
|
|
186
|
+
```mermaid
|
|
187
|
+
flowchart TD
|
|
188
|
+
A[Fehler im Worker] --> B{Verbindungsfehler?<br/>ECONNREFUSED / ETIMEDOUT /<br/>ECONNRESET / etc.}
|
|
189
|
+
|
|
190
|
+
B -- Ja --> C{Retries < Max?<br/>Standard: 6}
|
|
191
|
+
C -- Ja --> D[Exponentieller Backoff<br/>1s → 2s → 4s → … → 30s max]
|
|
192
|
+
D --> E[Erneut verbinden]
|
|
193
|
+
E --> F{Verbindung OK?}
|
|
194
|
+
F -- Ja --> G[Weiter arbeiten ✓]
|
|
195
|
+
F -- Nein --> C
|
|
196
|
+
|
|
197
|
+
C -- Nein --> H[Worker beenden<br/>Exit Code 3]
|
|
198
|
+
|
|
199
|
+
B -- Nein --> I{Uncaught Exception?}
|
|
200
|
+
I -- Ja --> J[Worker beenden<br/>Exit Code 4]
|
|
201
|
+
I -- Nein --> K[Fehler loggen,<br/>Worker beenden<br/>Exit Code 3]
|
|
202
|
+
|
|
203
|
+
H --> L[Adapter erkennt Exit]
|
|
204
|
+
J --> L
|
|
205
|
+
K --> L
|
|
206
|
+
|
|
207
|
+
L --> M{Restarts < 6 in<br/>letzten 5 Minuten?}
|
|
208
|
+
M -- Ja --> N[Exponentieller Backoff<br/>1s → 2s → 4s → … → 30s max]
|
|
209
|
+
N --> O[Worker neu starten]
|
|
210
|
+
O --> P{Start OK?}
|
|
211
|
+
P -- Ja --> G
|
|
212
|
+
P -- Nein --> L
|
|
213
|
+
|
|
214
|
+
M -- Nein --> Q[Worker bleibt gestoppt ✗<br/>Manueller Eingriff nötig]
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Worker-Level (Kindprozess):**
|
|
218
|
+
|
|
219
|
+
- Bei Verbindungsfehlern (ECONNREFUSED, ECONNRESET, ETIMEDOUT, ENOTFOUND, EAI_AGAIN, Socket Hang Up) versucht der Worker bis zu **6 Reconnects** mit exponentiellem Backoff (1s → 2s → 4s → 8s → 16s → 30s max).
|
|
220
|
+
- Die Anzahl der Retries ist über die Umgebungsvariable `PROCESSCUBE_APP_SDK_ETW_RETRY` konfigurierbar.
|
|
221
|
+
- Nach Ausschöpfung der Retries beendet sich der Worker mit Exit Code 3.
|
|
222
|
+
- Bei unbehandelten Exceptions beendet sich der Worker mit Exit Code 4.
|
|
223
|
+
|
|
224
|
+
**Adapter-Level (Hauptprozess):**
|
|
225
|
+
|
|
226
|
+
- Erkennt der Adapter einen Worker-Exit mit Code 3 oder 4, wird ein Neustart versucht.
|
|
227
|
+
- Maximal **6 Neustarts** innerhalb eines **5-Minuten-Fensters** sind erlaubt.
|
|
228
|
+
- Die Backoff-Zeiten steigen exponentiell: 1s → 2s → 4s → 8s → 16s → 30s.
|
|
229
|
+
- Wird das Limit erreicht, bleibt der Worker gestoppt — ein manueller Eingriff (z.B. App-Neustart) ist nötig.
|
|
230
|
+
- Nach Ablauf des 5-Minuten-Fensters wird der Zähler zurückgesetzt.
|
|
231
|
+
|
|
232
|
+
#### Setup und Konfiguration
|
|
233
|
+
|
|
234
|
+
##### 1. Next.js Plugin aktivieren
|
|
235
|
+
|
|
236
|
+
In der `next.config.js` wird das SDK-Plugin eingebunden und External Tasks aktiviert:
|
|
54
237
|
|
|
55
238
|
```javascript
|
|
56
239
|
// next.config.js
|
|
@@ -59,53 +242,91 @@ const { withApplicationSdk } = require('@5minds/processcube_app_sdk/server');
|
|
|
59
242
|
module.exports = withApplicationSdk({
|
|
60
243
|
applicationSdk: {
|
|
61
244
|
useExternalTasks: true,
|
|
245
|
+
// Optional: Eigenes Verzeichnis für External Tasks
|
|
246
|
+
// customExternalTasksDirPath: './my-tasks',
|
|
62
247
|
},
|
|
63
248
|
});
|
|
64
249
|
```
|
|
65
250
|
|
|
251
|
+
Das Plugin erkennt automatisch, ob die App im Development- oder Production-Modus läuft, und startet die Worker entsprechend. Während des Build-Prozesses (`next build`) werden keine Worker gestartet.
|
|
252
|
+
|
|
253
|
+
##### 2. Handler-Datei anlegen
|
|
254
|
+
|
|
255
|
+
External Tasks werden durch Dateien mit dem Namen `external_task.ts` (oder `.js`) definiert. Das Verzeichnis, in dem die Datei liegt, bestimmt automatisch das **Topic**, unter dem sich der Worker bei der Engine registriert.
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
app/
|
|
259
|
+
├── order/
|
|
260
|
+
│ └── process/
|
|
261
|
+
│ └── external_task.ts → Topic: order/process
|
|
262
|
+
├── invoice/
|
|
263
|
+
│ └── send/
|
|
264
|
+
│ └── external_task.ts → Topic: invoice/send
|
|
265
|
+
└── notification/
|
|
266
|
+
└── email/
|
|
267
|
+
└── external_task.ts → Topic: notification/email
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Das SDK sucht Handler-Dateien standardmäßig in `./app` oder `./src/app`. Ein eigenes Verzeichnis kann über `customExternalTasksDirPath` konfiguriert werden.
|
|
271
|
+
|
|
272
|
+
> **Wichtig:** Pro Verzeichnis darf nur eine `external_task.ts` oder `external_task.js` existieren. Beide Dateien im selben Verzeichnis führen zu einem Fehler.
|
|
273
|
+
|
|
66
274
|
#### Handler-Signatur
|
|
67
275
|
|
|
68
|
-
Der Handler wird als
|
|
276
|
+
Der Handler wird als **Default-Export** der Datei definiert. Er erhält bis zu drei Parameter:
|
|
69
277
|
|
|
70
278
|
```typescript
|
|
71
279
|
export default async function handleExternalTask(
|
|
72
|
-
payload: any,
|
|
73
|
-
task: ExternalTask<any>,
|
|
74
|
-
signal: AbortSignal,
|
|
280
|
+
payload: any,
|
|
281
|
+
task: ExternalTask<any>,
|
|
282
|
+
signal: AbortSignal,
|
|
75
283
|
) {
|
|
76
|
-
//
|
|
284
|
+
// Geschäftslogik hier
|
|
77
285
|
return { result: 'done' };
|
|
78
286
|
}
|
|
79
287
|
```
|
|
80
288
|
|
|
81
|
-
|
|
289
|
+
| Parameter | Typ | Beschreibung |
|
|
290
|
+
|---|---|---|
|
|
291
|
+
| `payload` | `any` | Die Prozessvariablen, die der BPMN-Prozess dem External Task mitgibt. Enthält die im Prozessmodell definierte Payload-Expression. |
|
|
292
|
+
| `task` | `ExternalTask<any>` | Metadaten des Tasks: `id`, `workerId`, `topic`, `correlationId`, `processInstanceId`, `processDefinitionId`, `flowNodeInstanceId`, `lockExpirationTime`, `state`, `createdAt`. Optional. |
|
|
293
|
+
| `signal` | `AbortSignal` | Wird ausgelöst, wenn ein Boundary Event (z.B. Timer) den Task abbricht. Optional. |
|
|
82
294
|
|
|
83
|
-
|
|
295
|
+
**Rückgabewert:** Das zurückgegebene Objekt wird als Ergebnis an die Engine gemeldet und steht im BPMN-Prozess als Variable zur Verfügung.
|
|
84
296
|
|
|
85
|
-
|
|
297
|
+
#### Worker-Konfiguration
|
|
298
|
+
|
|
299
|
+
Über einen benannten `config`-Export können Worker-Einstellungen pro Handler angepasst werden:
|
|
86
300
|
|
|
87
301
|
```typescript
|
|
88
302
|
import { ExternalTaskConfig } from '@5minds/processcube_app_sdk/server';
|
|
89
303
|
|
|
90
304
|
export const config: ExternalTaskConfig = {
|
|
91
|
-
lockDuration: 5000,
|
|
92
|
-
maxTasks: 5,
|
|
305
|
+
lockDuration: 5000, // Lock-Dauer in ms (Standard: 30000)
|
|
306
|
+
maxTasks: 5, // Gleichzeitige Tasks pro Polling-Zyklus (Standard: 10)
|
|
93
307
|
};
|
|
94
308
|
```
|
|
95
309
|
|
|
96
|
-
|
|
310
|
+
| Option | Typ | Standard | Beschreibung |
|
|
311
|
+
|---|---|---|---|
|
|
312
|
+
| `lockDuration` | `number` | `30000` | Dauer in Millisekunden, für die ein Task gesperrt wird. Bestimmt auch das Intervall der Lock-Verlängerung und die maximale Verzögerung bei Abort-Signalen. |
|
|
313
|
+
| `maxTasks` | `number` | `10` | Maximale Anzahl gleichzeitig abgeholter Tasks pro Polling-Zyklus. |
|
|
97
314
|
|
|
98
315
|
#### Abort-Handling bei Boundary Events
|
|
99
316
|
|
|
100
|
-
Wenn ein Boundary Event (z.B. ein Timer) einen External Task abbricht,
|
|
317
|
+
Wenn ein BPMN Boundary Event (z.B. ein Timer oder Signal) einen External Task abbricht, löst die Engine den Abbruch beim nächsten Lock-Renewal aus. Das `AbortSignal` im Handler wird daraufhin ausgelöst.
|
|
318
|
+
|
|
319
|
+
**Wichtig:** Die `lockDuration` bestimmt die maximale Verzögerung bis zum Abort, da die Engine den Abbruch erst beim nächsten Lock-Renewal mitteilen kann:
|
|
101
320
|
|
|
102
|
-
| lockDuration
|
|
103
|
-
|
|
104
|
-
| 30000 (
|
|
105
|
-
| 5000
|
|
106
|
-
| 1000
|
|
321
|
+
| lockDuration | Max. Verzögerung bis Abort |
|
|
322
|
+
|---|---|
|
|
323
|
+
| `30000` (Standard) | bis zu 30 Sekunden |
|
|
324
|
+
| `5000` | bis zu 5 Sekunden |
|
|
325
|
+
| `1000` | bis zu 1 Sekunde |
|
|
107
326
|
|
|
108
|
-
|
|
327
|
+
Für zeitkritische Abbrüche sollte die `lockDuration` entsprechend reduziert werden.
|
|
328
|
+
|
|
329
|
+
**Beispiel mit Abort-Handling:**
|
|
109
330
|
|
|
110
331
|
```typescript
|
|
111
332
|
import { ExternalTaskConfig } from '@5minds/processcube_app_sdk/server';
|
|
@@ -115,25 +336,133 @@ export const config: ExternalTaskConfig = {
|
|
|
115
336
|
};
|
|
116
337
|
|
|
117
338
|
export default async function handleExternalTask(payload: any, _task: any, signal: AbortSignal) {
|
|
339
|
+
// Listener für Cleanup-Aktionen bei Abbruch
|
|
118
340
|
signal.addEventListener(
|
|
119
341
|
'abort',
|
|
120
342
|
() => {
|
|
121
343
|
console.log('Task wurde durch Boundary Event abgebrochen');
|
|
344
|
+
// Hier ggf. Ressourcen freigeben
|
|
122
345
|
},
|
|
123
346
|
{ once: true },
|
|
124
347
|
);
|
|
125
348
|
|
|
126
|
-
// Signal vor
|
|
349
|
+
// Signal vor asynchronen Operationen prüfen
|
|
127
350
|
if (signal.aborted) return;
|
|
128
351
|
|
|
129
352
|
const result = await doWork(payload);
|
|
130
353
|
|
|
354
|
+
// Signal nach asynchronen Operationen prüfen
|
|
131
355
|
if (signal.aborted) return;
|
|
132
356
|
|
|
133
357
|
return result;
|
|
134
358
|
}
|
|
135
359
|
```
|
|
136
360
|
|
|
361
|
+
#### Authentifizierung und Token-Management
|
|
362
|
+
|
|
363
|
+
Ist eine ProcessCube Authority konfiguriert, holt der Adapter automatisch Tokens per **OpenID Connect Client Credentials Grant** und verteilt sie an alle Worker.
|
|
364
|
+
|
|
365
|
+
```mermaid
|
|
366
|
+
sequenceDiagram
|
|
367
|
+
participant Authority as OpenID Authority
|
|
368
|
+
participant Adapter as ExternalTaskAdapter
|
|
369
|
+
participant W1 as Worker 1
|
|
370
|
+
participant W2 as Worker 2
|
|
371
|
+
|
|
372
|
+
Adapter->>Authority: Client Credentials Grant<br/>(client_id, client_secret, scope: engine_etw)
|
|
373
|
+
Authority-->>Adapter: TokenSet (access_token, expires_in)
|
|
374
|
+
|
|
375
|
+
Adapter->>W1: IPC: create (mit Identity)
|
|
376
|
+
Adapter->>W2: IPC: create (mit Identity)
|
|
377
|
+
|
|
378
|
+
Note over Adapter: Token-Refresh bei 85%<br/>der Lebensdauer
|
|
379
|
+
|
|
380
|
+
Adapter->>Authority: Client Credentials Grant (Refresh)
|
|
381
|
+
Authority-->>Adapter: Neues TokenSet
|
|
382
|
+
|
|
383
|
+
Adapter->>W1: IPC: updateIdentity
|
|
384
|
+
Adapter->>W2: IPC: updateIdentity
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
- Der Token wird bei **85% seiner Lebensdauer** automatisch erneuert.
|
|
388
|
+
- Alle aktiven Worker erhalten den neuen Token per IPC-Nachricht.
|
|
389
|
+
- Der initiale Token-Abruf hat **10 Versuche** mit exponentiellem Backoff (max. 30s).
|
|
390
|
+
- Der periodische Token-Refresh versucht es **unbegrenzt** mit Backoff (max. 60s).
|
|
391
|
+
- Ist keine Authority konfiguriert, wird eine Dummy-Identity verwendet (für lokale Entwicklung ohne Auth).
|
|
392
|
+
|
|
393
|
+
#### Umgebungsvariablen
|
|
394
|
+
|
|
395
|
+
| Variable | Pflicht | Standard | Beschreibung |
|
|
396
|
+
|---|---|---|---|
|
|
397
|
+
| `PROCESSCUBE_ENGINE_URL` | Nein | `http://localhost:10560` | URL der ProcessCube Engine |
|
|
398
|
+
| `PROCESSCUBE_AUTHORITY_URL` | Nein | — | URL des OpenID-Providers. Wenn gesetzt, wird Token-basierte Authentifizierung aktiviert. |
|
|
399
|
+
| `PROCESSCUBE_EXTERNAL_TASK_WORKER_CLIENT_ID` | Wenn Authority | — | Client-ID für den OpenID Client Credentials Grant |
|
|
400
|
+
| `PROCESSCUBE_EXTERNAL_TASK_WORKER_CLIENT_SECRET` | Wenn Authority | — | Client-Secret für den OpenID Client Credentials Grant |
|
|
401
|
+
| `PROCESSCUBE_APP_SDK_ETW_RETRY` | Nein | `6` | Maximale Anzahl der Reconnect-Versuche im Worker-Prozess bei Verbindungsfehlern |
|
|
402
|
+
|
|
403
|
+
#### Vollständiges Beispiel
|
|
404
|
+
|
|
405
|
+
Eine `external_task.ts` mit allen Features — Konfiguration, typisiertem Payload, Fehlerbehandlung und Abort-Support:
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { ExternalTaskConfig } from '@5minds/processcube_app_sdk/server';
|
|
409
|
+
|
|
410
|
+
// Worker-Konfiguration
|
|
411
|
+
export const config: ExternalTaskConfig = {
|
|
412
|
+
lockDuration: 5000, // 5s Lock für schnelle Abort-Reaktion
|
|
413
|
+
maxTasks: 3, // Maximal 3 Tasks gleichzeitig
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// Typen für Payload und Ergebnis
|
|
417
|
+
interface OrderPayload {
|
|
418
|
+
orderId: string;
|
|
419
|
+
customerEmail: string;
|
|
420
|
+
items: Array<{ productId: string; quantity: number }>;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
interface OrderResult {
|
|
424
|
+
confirmationId: string;
|
|
425
|
+
processedAt: string;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export default async function handleExternalTask(
|
|
429
|
+
payload: OrderPayload,
|
|
430
|
+
task: any,
|
|
431
|
+
signal: AbortSignal,
|
|
432
|
+
): Promise<OrderResult | undefined> {
|
|
433
|
+
console.log(`Verarbeite Bestellung ${payload.orderId} (Task: ${task.id})`);
|
|
434
|
+
|
|
435
|
+
// Abort-Handler für Cleanup
|
|
436
|
+
signal.addEventListener(
|
|
437
|
+
'abort',
|
|
438
|
+
() => {
|
|
439
|
+
console.log(`Bestellung ${payload.orderId} wurde abgebrochen`);
|
|
440
|
+
},
|
|
441
|
+
{ once: true },
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
if (signal.aborted) return;
|
|
445
|
+
|
|
446
|
+
// Geschäftslogik
|
|
447
|
+
const confirmation = await processOrder(payload);
|
|
448
|
+
|
|
449
|
+
if (signal.aborted) return;
|
|
450
|
+
|
|
451
|
+
await sendConfirmationEmail(payload.customerEmail, confirmation);
|
|
452
|
+
|
|
453
|
+
if (signal.aborted) return;
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
confirmationId: confirmation.id,
|
|
457
|
+
processedAt: new Date().toISOString(),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
#### Hot-Reload
|
|
463
|
+
|
|
464
|
+
Im Development-Modus überwacht das SDK die Handler-Dateien per File-Watcher. Änderungen an einer `external_task.ts` werden automatisch erkannt: Die Datei wird neu transpiliert und der Worker per IPC-Nachricht (`restart`) mit dem neuen Code neu gestartet — ohne die App neu starten zu müssen. Die Worker-ID bleibt dabei erhalten.
|
|
465
|
+
|
|
137
466
|
## Wie kann ich das Projekt aufsetzen?
|
|
138
467
|
|
|
139
468
|
### Setup/Installation
|
package/package.json
CHANGED