Workflow-Integration
Die Workflow-Integration verbindet LowCode nahtlos mit der Engine. Sie können BPMN-Prozesse starten, auf Events reagieren, UserTasks bearbeiten und komplexe End-to-End-Workflows orchestrieren — alles innerhalb Ihrer Node-RED Flows.
Verfügbare Engine-Nodes
Das ProcessCube® Enterprise Image stellt folgende Nodes für die Engine-Integration bereit:
| Node | Funktion | Richtung |
|---|---|---|
process-start | BPMN-Prozess starten | Flow → Engine |
usertask-event-listener | Auf neue UserTasks reagieren | Engine → Flow |
usertask-output | UserTask-Ergebnis an Engine senden | Flow → Engine |
process-event-listener | Engine-Events abonnieren | Engine → Flow |
query-process-instances | Prozess-Instanzen abfragen | Flow → Engine |
signal-event | Signal-Events an Engine senden | Flow → Engine |
Prozesse starten
Einfacher Prozessstart
[http in: POST /api/start-process]
→ [fn: preparePayload]
→ [process-start: MyProcess_v1]
→ [fn: formatResponse]
→ [http response]// Function Node: preparePayload
const body = msg.payload;
// Prozess-Parameter setzen
msg.processModelId = "ApprovalProcess_v1";
msg.payload = {
applicantName: body.name,
requestType: body.type,
amount: body.amount
};
return msg;// Function Node: formatResponse
msg.statusCode = 201;
msg.payload = {
success: true,
processInstanceId: msg.processInstanceId,
message: "Prozess wurde gestartet"
};
return msg;Prozessstart mit Korrelation
Wenn Sie das Ergebnis eines gestarteten Prozesses abwarten möchten, verwenden Sie eine Korrelations-ID:
[http in: POST /api/request]
→ [fn: generateCorrelationId]
→ [process-start: RequestProcess]
→ [fn: storeCorrelation]
→ [http response: 202 Accepted]// Function Node: generateCorrelationId
const correlationId = `corr-${Date.now()}-${Math.random().toString(36).slice(2)}`;
msg.correlationId = correlationId;
msg.payload = {
...msg.payload,
correlationId: correlationId
};
return msg;UserTask-Handling
Das Herzstück der Workflow-Integration ist das UserTask-Handling. Dabei wartet die Engine auf eine menschliche Eingabe, die über LowCode bereitgestellt wird.
UserTask-Lifecycle
1. Engine erstellt UserTask → [usertask-event-listener] empfängt Event
2. LowCode zeigt Formular → [ui-dynamic-form] rendert FormFields
3. Benutzer füllt Formular aus → [usertask-output] sendet Ergebnis
4. Engine setzt Prozess fort → Nächster Schritt im BPMNUserTask empfangen und anzeigen
[usertask-event-listener: userTaskCreated]
→ [fn: enrichTaskData]
→ [fn: storeTask]
→ [ui-dynamic-form: Formular anzeigen]// Function Node: enrichTaskData
const userTask = msg.payload;
// UserTask-Daten für die Anzeige aufbereiten
msg.taskDisplay = {
id: userTask.id,
name: userTask.name,
processModelId: userTask.processModelId,
processInstanceId: userTask.processInstanceId,
formFields: userTask.formFields,
createdAt: new Date().toISOString()
};
// Im Flow-Context speichern für späteren Zugriff
const openTasks = flow.get("openTasks") || [];
openTasks.push(msg.taskDisplay);
flow.set("openTasks", openTasks);
node.status({
fill: "blue",
shape: "dot",
text: `${openTasks.length} offene Tasks`
});
return msg;UserTask beenden
[ui-dynamic-form: Ergebnis]
→ [fn: prepareFinish]
→ [usertask-output: UserTask beenden]
→ [fn: cleanUp]
→ [ui-notification: "Aufgabe erledigt"]// Function Node: prepareFinish
const formResult = msg.payload;
const taskId = msg.userTaskInstanceId;
// Ergebnis für die Engine vorbereiten
msg.userTaskInstanceId = taskId;
msg.payload = {
approved: formResult.approved,
comment: formResult.comment || "",
processedBy: formResult.processedBy
};
return msg;// Function Node: cleanUp
const taskId = msg.userTaskInstanceId;
// Erledigten Task aus der Liste entfernen
const openTasks = flow.get("openTasks") || [];
const updated = openTasks.filter(t => t.id !== taskId);
flow.set("openTasks", updated);
msg.payload = `Aufgabe ${taskId.substring(0, 8)} erledigt`;
node.status({
fill: "green",
shape: "dot",
text: `${updated.length} offene Tasks`
});
return msg;Engine-Events abonnieren
Mit dem process-event-listener-Node reagieren Sie auf alle Events der Engine.
Verfügbare Event-Typen
| Event | Beschreibung | Typischer Einsatz |
|---|---|---|
processStarted | Prozess wurde gestartet | Logging, Monitoring |
processFinished | Prozess wurde beendet | Benachrichtigungen |
processError | Fehler im Prozess | Alerting, Error Handling |
userTaskCreated | Neuer UserTask erstellt | UI-Anzeige |
userTaskFinished | UserTask wurde beendet | Statistiken |
signalReceived | Signal-Event empfangen | Cross-Prozess-Kommunikation |
Monitoring-Flow
[process-event-listener: processStarted]
→ [fn: logStart]
→ [fn: updateDashboard]
→ [ui-chart: Prozesse pro Stunde]
[process-event-listener: processError]
→ [fn: formatAlert]
→ [email: Ops-Team benachrichtigen]// Function Node: logStart
const event = msg.payload;
// Statistik aktualisieren
const stats = flow.get("processStats") || { started: 0, finished: 0, errors: 0 };
stats.started++;
flow.set("processStats", stats);
// Für Dashboard aufbereiten
msg.payload = stats.started;
msg.topic = "Gestartete Prozesse";
return msg;Prozess-Instanzen abfragen
Fragen Sie den aktuellen Status von Prozess-Instanzen ab:
[http in: GET /api/processes]
→ [query-process-instances]
→ [fn: formatResult]
→ [http response]// Function Node: formatResult
const instances = msg.payload || [];
msg.statusCode = 200;
msg.payload = {
success: true,
count: instances.length,
data: instances.map(pi => ({
id: pi.id,
processModelId: pi.processModelId,
state: pi.state,
startedAt: pi.createdAt,
finishedAt: pi.finishedAt || null
}))
};
return msg;Vollständiges E2E-Workflow-Beispiel
Das folgende Beispiel zeigt einen kompletten End-to-End-Workflow: Ein HTTP-Request startet einen Genehmigungsprozess, ein Sachbearbeiter bearbeitet den UserTask über ein dynamisches Formular, und das Ergebnis wird als HTTP-Response zurückgegeben.
BPMN-Prozess: Genehmigung
[Start] → [UserTask: Antrag prüfen] → [Gateway: Genehmigt?]
├─ Ja → [ServiceTask: Bestätigung senden] → [End]
└─ Nein → [ServiceTask: Absage senden] → [End]Flow 1: Prozess starten (HTTP-Trigger)
[http in: POST /api/approval]
→ [fn: validateRequest]
→ [fn: prepareProcess]
→ [process-start: ApprovalProcess]
→ [fn: storeCorrelation]
→ [http response: 202 Accepted]// Function Node: validateRequest
const body = msg.payload;
if (!body.applicant || !body.amount) {
msg.statusCode = 400;
msg.payload = {
success: false,
error: "Felder 'applicant' und 'amount' sind erforderlich"
};
return [null, msg]; // Zweiter Ausgang: Fehler
}
return [msg, null]; // Erster Ausgang: Weiter// Function Node: prepareProcess
msg.processModelId = "ApprovalProcess_v1";
msg.correlationId = `approval-${Date.now()}`;
msg.payload = {
applicant: msg.payload.applicant,
amount: msg.payload.amount,
reason: msg.payload.reason || "",
correlationId: msg.correlationId
};
return msg;Flow 2: UserTask bearbeiten (Dashboard)
[usertask-event-listener: userTaskCreated]
→ [fn: filterApprovalTasks]
→ [fn: prepareFormData]
→ [ui-dynamic-form: Antrag prüfen]
→ [fn: mapFormResult]
→ [usertask-output: Ergebnis senden]
→ [fn: notifyComplete]
→ [ui-notification: "Antrag bearbeitet"]// Function Node: filterApprovalTasks
const userTask = msg.payload;
// Nur UserTasks des Genehmigungsprozesses verarbeiten
if (userTask.processModelId !== "ApprovalProcess_v1") {
return null; // Flow für andere Tasks stoppen
}
return msg;// Function Node: prepareFormData
const userTask = msg.payload;
msg.userTaskInstanceId = userTask.id;
msg.userTaskConfig = {
userTaskInstanceId: userTask.id,
title: `Antrag von ${userTask.tokens[0]?.payload?.applicant || "Unbekannt"}`,
formFields: userTask.formFields
};
return msg;// Function Node: mapFormResult
const formResult = msg.payload;
msg.payload = {
approved: formResult.decision === "approved",
comment: formResult.comment || "",
reviewedAt: new Date().toISOString()
};
return msg;Flow 3: Ergebnis abfragen (Status-API)
[http in: GET /api/approval/:correlationId]
→ [query-process-instances]
→ [fn: findByCorrelation]
→ [fn: formatStatus]
→ [http response]// Function Node: findByCorrelation
const correlationId = msg.req.params.correlationId;
const instances = msg.payload || [];
const instance = instances.find(
pi => pi.correlationId === correlationId
);
if (!instance) {
msg.statusCode = 404;
msg.payload = { success: false, error: "Antrag nicht gefunden" };
return [null, msg];
}
msg.processInstance = instance;
return [msg, null];// Function Node: formatStatus
const pi = msg.processInstance;
msg.statusCode = 200;
msg.payload = {
success: true,
data: {
correlationId: msg.req.params.correlationId,
state: pi.state,
approved: pi.tokens?.[0]?.payload?.approved ?? null,
startedAt: pi.createdAt,
finishedAt: pi.finishedAt || null
}
};
return msg;Best Practices
1. Events statt Polling
Nutzen Sie die Event-Listener der Engine anstatt regelmäßig nach neuen UserTasks zu fragen:
// Korrekt: Event-basiert
[usertask-event-listener] → [Verarbeitung]
// Vermeiden: Polling
[inject: Alle 5s] → [query-user-tasks] → [Verarbeitung]2. Fehlerbehandlung im Prozess
Fangen Sie Fehler ab und geben Sie sie an die Engine zurück:
[catch: Engine-Nodes]
→ [fn: logError]
→ [fn: sendErrorToEngine]
→ [debug]3. Korrelation nutzen
Verwenden Sie Korrelations-IDs, um HTTP-Requests mit Prozess-Instanzen zu verknüpfen. So können Clients den Status asynchroner Prozesse abfragen.
4. UserTasks im Context verwalten
Speichern Sie offene UserTasks im Flow-Context, um sie in Dashboard-Tabellen und Statistiken anzuzeigen:
// Offene Tasks pflegen
const tasks = flow.get("openTasks") || [];
tasks.push(newTask);
flow.set("openTasks", tasks);5. Environment Variables für Engine-URLs
Konfigurieren Sie die Engine-URL immer als Environment Variable:
const engineUrl = env.get("ENGINE_URL"); // z.B. http://engine:8000Nächste Schritte
- User Interfaces erstellen — Dashboard-2 Widgets für UserTask-UIs
- Engine-Nodes — Detaillierte Node-Referenz
- ProcessCube® Konzepte — Event-Driven Architecture Patterns
- Portal-Integration — UserTask-UIs als Portal-Apps bereitstellen