Handler entwickeln
External Task Handler werden als Dateien im App-Verzeichnis von Next.js angelegt. Das App-SDK erkennt sie automatisch und startet Worker-Prozesse dafür.
Dateikonvention
Der Dateiname muss external_task.ts oder external_task.js lauten. Die Datei wird in einem Unterverzeichnis von app/ platziert. Der Verzeichnisname bestimmt das Topic.
- external_task.ts
- external_task.ts
- external_task.ts
Diese Struktur erzeugt drei Worker für die Topics filter_email, forward_email und delete_email.
Pro Verzeichnis darf nur eine External-Task-Datei existieren. Werden sowohl external_task.ts als auch external_task.js gefunden, wird kein Worker gestartet.
Topic-Ableitung
Das Topic ergibt sich aus dem relativen Pfad des Verzeichnisses zum App-Ordner:
| Dateipfad | Topic |
|---|---|
app/filter_email/external_task.ts | filter_email |
app/orders/validate/external_task.ts | orders/validate |
app/(workers)/send_email/external_task.ts | send_email |
Route Groups (Verzeichnisse in Klammern wie (workers)) werden aus dem Topic entfernt. Siehe Erweiterte Konzepte.
Handler-Signatur
Die Handler-Funktion muss als default-Export bereitgestellt werden:
export default async function handler(
payload: TPayload,
externalTask?: ExternalTask<TPayload>,
signal?: AbortSignal
): Promise<TResult>Parameter
| Parameter | Typ | Beschreibung |
|---|---|---|
payload | TPayload | Die Prozessdaten (Token) des External Service Tasks |
externalTask | ExternalTask<TPayload> (optional) | Das vollständige External-Task-Objekt mit Metadaten |
signal | AbortSignal (optional) | Signal zum Abbruch bei Boundary Events |
Rückgabewert
Der Rückgabewert wird als Ergebnis-Token des Service Tasks in den Prozess geschrieben. Die Engine nutzt diesen Wert für nachfolgende Gateways und Aktivitäten.
Beispiele
Einfacher Handler
export default async function filterEmail(payload: any) {
const isSpam = payload.subject?.toLowerCase().includes('spam');
return { is_spam: isSpam };
}Handler mit typisierten Payload
interface PricePayload {
productId: string;
quantity: number;
discount?: number;
}
interface PriceResult {
totalPrice: number;
currency: string;
}
export default async function calculatePrice(
payload: PricePayload
): Promise<PriceResult> {
const basePrice = await fetchProductPrice(payload.productId);
const total = basePrice * payload.quantity * (1 - (payload.discount ?? 0));
return {
totalPrice: total,
currency: 'EUR',
};
}
async function fetchProductPrice(productId: string): Promise<number> {
// Preisermittlung aus Datenbank oder API
return 29.99;
}Handler mit ExternalTask-Objekt
import { ExternalTask } from '@5minds/processcube_engine_sdk';
interface AuditPayload {
action: string;
userId: string;
}
export default async function auditLog(
payload: AuditPayload,
externalTask?: ExternalTask<AuditPayload>
) {
console.log(`Audit: ${payload.action} von ${payload.userId}`);
console.log(`Prozess-Instanz: ${externalTask?.processInstanceId}`);
console.log(`Correlation: ${externalTask?.correlationId}`);
return { logged: true };
}Handler mit AbortSignal
export const config = {
lockDuration: 5000,
};
export default async function longRunningTask(
payload: any,
_task: any,
signal: AbortSignal
) {
signal.addEventListener('abort', () => {
console.log('Task wurde durch Boundary Event abgebrochen');
}, { once: true });
// Langwierige Operation mit Abbruch-Unterstützung
const result = await fetchWithTimeout(payload.url, signal);
if (signal.aborted) return;
return { data: result };
}
async function fetchWithTimeout(url: string, signal: AbortSignal) {
const response = await fetch(url, { signal });
return response.json();
}Fehlerbehandlung
Wirft der Handler einen Fehler, wird dieser als BPMN-Error an die Engine gemeldet. Ein angehängtes Error-Boundary-Event kann den Fehler im Prozess behandeln.
export default async function validateOrder(payload: any) {
if (!payload.orderId) {
throw new Error('Keine Order-ID vorhanden');
}
const order = await loadOrder(payload.orderId);
if (!order) {
throw new Error(`Order ${payload.orderId} nicht gefunden`);
}
return { valid: true, orderTotal: order.total };
}
async function loadOrder(orderId: string) {
// Bestellung aus Datenbank laden
return { total: 99.99 };
}Bei einem Fehler wird der Worker nicht beendet. Er verarbeitet weiterhin neue Tasks. Nur bei kritischen Fehlern (z.B. Verbindungsabbruch) greift der automatische Neustart.