Das SDK beinhaltet Komponenten und Funktionen für Frontend und Backend (Client/Server) zur einfachen und schnellen Entwicklung einer ProcessCube App auf Basis von Next.js.
- NodeJS
>= v24
npm i @5minds/processcube_app_sdkDas NPM Paket hat drei Exports.
Hier werden Komponenten und Funktionen exportiert, die im Client und Server genutzt werden können.
Zum Beispiel die React Komponente RemoteUserTask:
import { RemoteUserTask } from '@5minds/processcube_app_sdk';Hier steht alles ausschließlich für eine serverseitige Umgebung zur Verfügung. Dazu zählen Funktionen, die mit der Engine arbeiten, oder React Komponenten, die Serverseitig gerendert werden können.
Beispiel:
import { startProcess } from '@5minds/processcube_app_sdk/server';Um die Engine URL anzupassen, die von den exportierten Funktionen genutzt wird, muss PROCESSCUBE_ENGINE_URL als Umgebungsvariable gesetzt werden. Andernfalls wird localhost mit dem Standardport der Engine genutzt 10560.
Es können nur Komponenten und Funktionen importiert werden, die im Browser funktionieren. Zum Beispiel React Komponenten, die einen clientseitigen Router und dessen React Hooks nutzen oder Funktionen, die auf window oder generell globale Browser APIs zugreifen möchten.
import { DynamicLink } from '@5minds/processcube_app_sdk/client';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.
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.
Das folgende Diagramm zeigt die drei beteiligten Schichten und ihre Kommunikation:
graph LR
subgraph ProcessCube Engine
E[Engine]
end
subgraph Next.js App – Hauptprozess
A[ExternalTaskAdapter]
W[File Watcher]
T[Token Management]
end
subgraph Eigener Node.js Prozess je Topic
WP1[Worker Process<br/>Topic: order/process]
WP2[Worker Process<br/>Topic: invoice/send]
end
E -- "HTTP: Fetch & Lock /<br/>Finish / Error /<br/>Extend Lock" --> WP1
E -- "HTTP: Fetch & Lock /<br/>Finish / Error /<br/>Extend Lock" --> WP2
A -- "IPC: create / restart /<br/>updateIdentity" --> WP1
A -- "IPC: create / restart /<br/>updateIdentity" --> WP2
W -- "Dateiänderung erkannt" --> A
T -- "Token-Refresh" --> A
Hauptkomponenten:
| Komponente | Aufgabe |
|---|---|
| ExternalTaskAdapter | Läuft im Hauptprozess der Next.js App. Überwacht das Dateisystem, startet Worker-Prozesse, verwaltet Tokens und koordiniert Restarts. |
| 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. |
| ProcessCube Engine | Verwaltet BPMN-Prozesse und vergibt External Tasks an Worker über das Fetch-and-Lock-Protokoll. |
Das folgende Sequenzdiagramm zeigt, wie ein External Task von der Engine zum Worker gelangt, verarbeitet und abgeschlossen wird:
sequenceDiagram
participant Engine as ProcessCube Engine
participant Worker as Worker Process
loop Polling-Schleife
Worker->>Engine: POST /external_tasks/fetch_and_lock<br/>(workerId, topic, maxTasks, lockDuration)
Note right of Engine: Long-Polling — Anfrage wartet<br/>bis Task verfügbar oder Timeout
Engine-->>Worker: ExternalTask[] (mit Payload)
end
Note over Worker: Handler-Funktion wird aufgerufen:<br/>handler(payload, task, signal)
loop Lock-Verlängerung
Worker->>Engine: PUT /external_tasks/{id}/extend_lock<br/>(additionalDuration)
Note right of Engine: Lock wird verlängert,<br/>damit der Task nicht abläuft
end
alt Erfolg
Worker->>Engine: PUT /external_tasks/{id}/finish<br/>(result)
Note right of Engine: Ergebnis wird als<br/>Prozessvariable gespeichert
else Fehler
Worker->>Engine: PUT /external_tasks/{id}/error<br/>(errorCode, errorMessage)
Note right of Engine: Fehler wird im<br/>Prozess behandelt
end
Ablauf im Detail:
- Der Worker pollt die Engine per HTTP-Long-Polling nach neuen Tasks für sein Topic.
- Sobald ein Task verfügbar ist, sperrt die Engine ihn (Lock) und liefert ihn mit dem Payload aus.
- Der Worker ruft die Handler-Funktion auf und übergibt Payload, Task-Metadaten und ein AbortSignal.
- Während der Verarbeitung verlängert der Worker automatisch den Lock, damit die Engine den Task nicht vorzeitig freigibt.
- Nach Abschluss meldet der Worker das Ergebnis (Finish) oder einen Fehler (Error) an die Engine.
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):
sequenceDiagram
participant FS as Dateisystem
participant Adapter as ExternalTaskAdapter<br/>(Hauptprozess)
participant Worker as Worker Process<br/>(Kindprozess)
participant Engine as ProcessCube Engine
FS->>Adapter: Neue Datei erkannt:<br/>app/order/process/external_task.ts
Adapter->>Adapter: Datei transpilieren (esbuild)
Adapter->>Adapter: Topic ableiten: order/process
Adapter->>Worker: fork() — neuer Node.js-Prozess
Adapter->>Worker: IPC: { action: "create",<br/>topic, identity, moduleString }
Worker->>Worker: Handler-Modul aus String laden
Worker->>Engine: Polling starten (Fetch & Lock)
Note over Worker,Engine: Worker verarbeitet Tasks…
Note over FS: Datei wird geändert
FS->>Adapter: Dateiänderung erkannt
Adapter->>Adapter: Datei neu transpilieren
Adapter->>Worker: IPC: { action: "restart",<br/>topic, identity, moduleString }
Worker->>Worker: Alten Worker stoppen
Worker->>Worker: Neuen Handler laden
Worker->>Engine: Polling neu starten
Note over Adapter: Token läuft ab (85% Lebensdauer)
Adapter->>Adapter: Neuen Token holen (OpenID Connect)
Adapter->>Worker: IPC: { action: "updateIdentity",<br/>identity }
IPC-Nachrichten:
| Action | Richtung | Beschreibung |
|---|---|---|
create |
Adapter → Worker | Initialer Start: Übergibt Topic, Identity und transpilierten Handler-Code |
restart |
Adapter → Worker | Hot-Reload: Stoppt den alten Worker und startet mit neuem Code (gleiche Worker-ID) |
updateIdentity |
Adapter → Worker | Aktualisiert den Auth-Token auf dem laufenden Worker |
Das System hat zwei Ebenen der Fehlerbehandlung: im Worker-Prozess selbst und im Adapter (Hauptprozess).
flowchart TD
A[Fehler im Worker] --> B{Verbindungsfehler?<br/>ECONNREFUSED / ETIMEDOUT /<br/>ECONNRESET / etc.}
B -- Ja --> C{Retries < Max?<br/>Standard: 6}
C -- Ja --> D[Exponentieller Backoff<br/>1s → 2s → 4s → … → 30s max]
D --> E[Erneut verbinden]
E --> F{Verbindung OK?}
F -- Ja --> G[Weiter arbeiten ✓]
F -- Nein --> C
C -- Nein --> H[Worker beenden<br/>Exit Code 3]
B -- Nein --> I{Uncaught Exception?}
I -- Ja --> J[Worker beenden<br/>Exit Code 4]
I -- Nein --> K[Fehler loggen,<br/>Worker beenden<br/>Exit Code 3]
H --> L[Adapter erkennt Exit]
J --> L
K --> L
L --> M{Restarts < 6 in<br/>letzten 5 Minuten?}
M -- Ja --> N[Exponentieller Backoff<br/>1s → 2s → 4s → … → 30s max]
N --> O[Worker neu starten]
O --> P{Start OK?}
P -- Ja --> G
P -- Nein --> L
M -- Nein --> Q[Worker bleibt gestoppt ✗<br/>Manueller Eingriff nötig]
Worker-Level (Kindprozess):
- 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).
- Die Anzahl der Retries ist über die Umgebungsvariable
PROCESSCUBE_APP_SDK_ETW_RETRYkonfigurierbar. - Nach Ausschöpfung der Retries beendet sich der Worker mit Exit Code 3.
- Bei unbehandelten Exceptions beendet sich der Worker mit Exit Code 4.
Adapter-Level (Hauptprozess):
- Erkennt der Adapter einen Worker-Exit mit Code 3 oder 4, wird ein Neustart versucht.
- Maximal 6 Neustarts innerhalb eines 5-Minuten-Fensters sind erlaubt.
- Die Backoff-Zeiten steigen exponentiell: 1s → 2s → 4s → 8s → 16s → 30s.
- Wird das Limit erreicht, bleibt der Worker gestoppt — ein manueller Eingriff (z.B. App-Neustart) ist nötig.
- Nach Ablauf des 5-Minuten-Fensters wird der Zähler zurückgesetzt.
In der next.config.js wird das SDK-Plugin eingebunden und External Tasks aktiviert:
// next.config.js
const { withApplicationSdk } = require('@5minds/processcube_app_sdk/server');
module.exports = withApplicationSdk({
applicationSdk: {
useExternalTasks: true,
// Optional: Eigenes Verzeichnis für External Tasks
// customExternalTasksDirPath: './my-tasks',
},
});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.
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.
app/
├── order/
│ └── process/
│ └── external_task.ts → Topic: order/process
├── invoice/
│ └── send/
│ └── external_task.ts → Topic: invoice/send
└── notification/
└── email/
└── external_task.ts → Topic: notification/email
Das SDK sucht Handler-Dateien standardmäßig in ./app oder ./src/app. Ein eigenes Verzeichnis kann über customExternalTasksDirPath konfiguriert werden.
Wichtig: Pro Verzeichnis darf nur eine
external_task.tsoderexternal_task.jsexistieren. Beide Dateien im selben Verzeichnis führen zu einem Fehler.
Der Handler wird als Default-Export der Datei definiert. Er erhält bis zu drei Parameter:
export default async function handleExternalTask(payload: any, task: ExternalTask<any>, signal: AbortSignal) {
// Geschäftslogik hier
return { result: 'done' };
}| Parameter | Typ | Beschreibung |
|---|---|---|
payload |
any |
Die Prozessvariablen, die der BPMN-Prozess dem External Task mitgibt. Enthält die im Prozessmodell definierte Payload-Expression. |
task |
ExternalTask<any> |
Metadaten des Tasks: id, workerId, topic, correlationId, processInstanceId, processDefinitionId, flowNodeInstanceId, lockExpirationTime, state, createdAt. Optional. |
signal |
AbortSignal |
Wird ausgelöst, wenn ein Boundary Event (z.B. Timer) den Task abbricht. Optional. |
Rückgabewert: Das zurückgegebene Objekt wird als Ergebnis an die Engine gemeldet und steht im BPMN-Prozess als Variable zur Verfügung.
Über einen benannten config-Export können Worker-Einstellungen pro Handler angepasst werden:
import { ExternalTaskConfig } from '@5minds/processcube_app_sdk/server';
export const config: ExternalTaskConfig = {
lockDuration: 5000, // Lock-Dauer in ms (Standard: 30000)
maxTasks: 5, // Gleichzeitige Tasks pro Polling-Zyklus (Standard: 10)
};| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
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. |
maxTasks |
number |
10 |
Maximale Anzahl gleichzeitig abgeholter Tasks pro Polling-Zyklus. |
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.
Wichtig: Die lockDuration bestimmt die maximale Verzögerung bis zum Abort, da die Engine den Abbruch erst beim nächsten Lock-Renewal mitteilen kann:
| lockDuration | Max. Verzögerung bis Abort |
|---|---|
30000 (Standard) |
bis zu 30 Sekunden |
5000 |
bis zu 5 Sekunden |
1000 |
bis zu 1 Sekunde |
Für zeitkritische Abbrüche sollte die lockDuration entsprechend reduziert werden.
Beispiel mit Abort-Handling:
import { ExternalTaskConfig } from '@5minds/processcube_app_sdk/server';
export const config: ExternalTaskConfig = {
lockDuration: 5000,
};
export default async function handleExternalTask(payload: any, _task: any, signal: AbortSignal) {
// Listener für Cleanup-Aktionen bei Abbruch
signal.addEventListener(
'abort',
() => {
console.log('Task wurde durch Boundary Event abgebrochen');
// Hier ggf. Ressourcen freigeben
},
{ once: true },
);
// Signal vor asynchronen Operationen prüfen
if (signal.aborted) return;
const result = await doWork(payload);
// Signal nach asynchronen Operationen prüfen
if (signal.aborted) return;
return result;
}Ist eine ProcessCube Authority konfiguriert, holt der Adapter automatisch Tokens per OpenID Connect Client Credentials Grant und verteilt sie an alle Worker.
sequenceDiagram
participant Authority as OpenID Authority
participant Adapter as ExternalTaskAdapter
participant W1 as Worker 1
participant W2 as Worker 2
Adapter->>Authority: Client Credentials Grant<br/>(client_id, client_secret, scope: engine_etw)
Authority-->>Adapter: TokenSet (access_token, expires_in)
Adapter->>W1: IPC: create (mit Identity)
Adapter->>W2: IPC: create (mit Identity)
Note over Adapter: Token-Refresh bei 85%<br/>der Lebensdauer
Adapter->>Authority: Client Credentials Grant (Refresh)
Authority-->>Adapter: Neues TokenSet
Adapter->>W1: IPC: updateIdentity
Adapter->>W2: IPC: updateIdentity
- Der Token wird bei 85% seiner Lebensdauer automatisch erneuert.
- Alle aktiven Worker erhalten den neuen Token per IPC-Nachricht.
- Der initiale Token-Abruf hat 10 Versuche mit exponentiellem Backoff (max. 30s).
- Der periodische Token-Refresh versucht es unbegrenzt mit Backoff (max. 60s).
- Ist keine Authority konfiguriert, wird eine Dummy-Identity verwendet (für lokale Entwicklung ohne Auth).
| Variable | Pflicht | Standard | Beschreibung |
|---|---|---|---|
PROCESSCUBE_ENGINE_URL |
Nein | http://localhost:10560 |
URL der ProcessCube Engine |
PROCESSCUBE_AUTHORITY_URL |
Nein | — | URL des OpenID-Providers. Wenn gesetzt, wird Token-basierte Authentifizierung aktiviert. |
PROCESSCUBE_EXTERNAL_TASK_WORKER_CLIENT_ID |
Wenn Authority | — | Client-ID für den OpenID Client Credentials Grant |
PROCESSCUBE_EXTERNAL_TASK_WORKER_CLIENT_SECRET |
Wenn Authority | — | Client-Secret für den OpenID Client Credentials Grant |
PROCESSCUBE_APP_SDK_ETW_RETRY |
Nein | 6 |
Maximale Anzahl der Reconnect-Versuche im Worker-Prozess bei Verbindungsfehlern |
Eine external_task.ts mit allen Features — Konfiguration, typisiertem Payload, Fehlerbehandlung und Abort-Support:
import { ExternalTaskConfig } from '@5minds/processcube_app_sdk/server';
// Worker-Konfiguration
export const config: ExternalTaskConfig = {
lockDuration: 5000, // 5s Lock für schnelle Abort-Reaktion
maxTasks: 3, // Maximal 3 Tasks gleichzeitig
};
// Typen für Payload und Ergebnis
interface OrderPayload {
orderId: string;
customerEmail: string;
items: Array<{ productId: string; quantity: number }>;
}
interface OrderResult {
confirmationId: string;
processedAt: string;
}
export default async function handleExternalTask(payload: OrderPayload, task: any, signal: AbortSignal): Promise<OrderResult | undefined> {
console.log(`Verarbeite Bestellung ${payload.orderId} (Task: ${task.id})`);
// Abort-Handler für Cleanup
signal.addEventListener(
'abort',
() => {
console.log(`Bestellung ${payload.orderId} wurde abgebrochen`);
},
{ once: true },
);
if (signal.aborted) return;
// Geschäftslogik
const confirmation = await processOrder(payload);
if (signal.aborted) return;
await sendConfirmationEmail(payload.customerEmail, confirmation);
if (signal.aborted) return;
return {
confirmationId: confirmation.id,
processedAt: new Date().toISOString(),
};
}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.
Das SDK wird über den Node Paketmanager npm gebaut.
Für das Installieren und Bauen können folgende Befehle benutzt werden:
npm ci
npm run buildFür ein Productionbuild:
npm run build:prodUm mit dem Paket lokal zu arbeiten, kann es mit npm in ein anderes Projekt verlinkt werden:
npm link
npm run watchIm Zielprojekt anschließend:
npm link @5minds/processcube_app_sdkBei Problemen mit React muss ggf. noch die React Dependency des Zielprojekts zurück in das SDK gelinkt werden, damit nur eine React Instanz zur Laufzeit existiert:
npm link <path-to-project>/node_modules/react