Cascading System
Das Cascading-System propagiert Status-Änderungen automatisch entlang der ITSM-Kette Problem → Incident → Ticket. Wenn ein Problem oder Incident den Status wechselt, werden alle nachgelagerten verlinkten Entitäten aktualisiert: Activity-Logs, Notifications, SLA-Pause/Resume und WebSocket-Updates — vollständig asynchron, ohne den auslösenden Request zu blockieren.
Architektur: Transactional Outbox + BullMQ
Die Status-Mutation und das Cascading-Event sind atomar gekoppelt, die Verarbeitung selbst ist davon entkoppelt. Das verhindert sowohl verlorene Kaskaden (Event committet, aber Worker stürzt ab) als auch blockierte Requests (User wartet nicht auf die Kaskade).
TRIGGER (synchronous, in one transaction)
ProblemMutationService / IncidentStatusService
│ BEGIN TX
│ ├─ Change status (RESOLVED / CLOSED / …)
│ └─ INSERT CascadingEvent (status = PENDING) ← Outbox
│ COMMIT
│
└─ cascadingQueue.enqueue(eventId) ← after commit
PROCESSING (async, BullMQ worker in backend)
Queue "cascading-events" (Redis)
│
▼
processCascadingEvent(eventId)
├─ Claim: PENDING → PROCESSING (atomic)
├─ INCIDENT → linked tickets
│ PROBLEM → tickets + incidents + tickets-of-incidents (2-hop)
├─ per target: activity log (System) + SLA + notification + WebSocket
└─ status → PROCESSED, audit CASCADING_PROCESSED
Läuft im: Backend-Container. Die Verarbeitung startet mit dem Backend, nutzt Redis als Queue und reiht beim Start offene Events neu ein.
CascadingEvent (Outbox-Tabelle)
| Feld | Werte | Beschreibung |
|---|---|---|
entityType | INCIDENT, PROBLEM | Quell-Entität der Kaskade |
entityId | ID | Quell-Entität |
event | RESOLVED, CLOSED, MITIGATED, REOPENED, WORKAROUND_UPDATED | Was passiert ist |
payload | JSON | resolution, rootCause, mitigation, workaround, userId (für WebSocket-Echo-Suppression) |
status | PENDING → PROCESSING → PROCESSED | FAILED | Lebenszyklus |
attempts | Int | Zähler (Dead-Letter nach 3) |
error / processedAt | Text / DateTime | Fehlertext bzw. Zeitpunkt der erfolgreichen Verarbeitung |
Lebenszyklus
PENDING ──claim──▶ PROCESSING ──ok──▶ PROCESSED ▲ │ │ └──error──▶ attempts < 3 ──▶ PENDING (Retry mit Backoff) │ attempts ≥ 3 ──▶ FAILED (Dead-Letter + Audit) │ └─ Recovery beim Start: PENDING/PROCESSING-Events älter als 10 s werden neu eingereiht
Propagations-Richtung
| Quelle | Ziele | Verknüpfung |
|---|---|---|
| Incident | verlinkte Tickets (1-Hop) | ticketOnIncident |
| Problem | direkt verlinkte Tickets (1-Hop) | ticketOnProblem |
| verlinkte Incidents (1-Hop) | incidentOnProblem |
|
| Tickets dieser Incidents (2-Hop) | Incident → ticketOnIncident |
Doppel-Vermeidung: Ein Ticket, das sowohl direkt mit dem Problem als auch über einen Incident verlinkt ist, erhält nur den direkten Eintrag — der 2-Hop-Pfad überspringt bereits direkt verlinkte Tickets.
Event-Typen & Wirkung
| Event | Quelle | Propagiert an | Besonderheit |
|---|---|---|---|
RESOLVED | Incident / Problem | Tickets (+ Incidents + 2-Hop bei Problem) | SLA-Resume; Kunde wird benachrichtigt |
CLOSED | Incident / Problem | Tickets (+ Incidents + 2-Hop bei Problem) | SLA-Resume (Incident) |
REOPENED | Incident / Problem | Tickets (+ Incidents + 2-Hop bei Problem) | Incident-REOPENED → SLA erneut pausiert; „Resolution invalidiert" |
MITIGATED | Incident | Tickets | nur Activity + Notification (kein SLA-Resume) |
WORKAROUND_UPDATED | Problem | NUR Incidents | kein Ticket-Eintrag, kein 2-Hop |
Seiteneffekte pro Ziel
1. Activity-Log (System-Actor)
- Ticket: TicketMessage
type=ACTIVITY,activityData.type=CASCADING_UPDATE - Incident: IncidentActivity
CASCADING_UPDATE - Autor ist „System“. Der auslösende Benutzer erscheint nicht als Autor; sein Client erhält kein doppeltes Live-Update.
- Geschlossene Tickets (CLOSED/SPAM) und geschlossene Incidents (CLOSED) werden übersprungen.
2. SLA-Pause / Resume (Incident → Ticket)
RESOLVED/CLOSED→ Ticket-SLA läuft weiterREOPENED→ Ticket-SLA wird erneut pausiert- Dies ist die Laufzeit-Seite des SLA-Flags
pauseOnIncidentLink(SLA).
3. Notifications
- Benachrichtigt werden Agent (IN_APP) und Kunde (E-Mail + IN_APP).
- Kunden werden nur bei
RESOLVEDundREOPENEDbenachrichtigt. - Notification-Typen:
LINKED_INCIDENT_{RESOLVED,CLOSED,MITIGATED,REOPENED},LINKED_PROBLEM_{RESOLVED,CLOSED,REOPENED,WORKAROUND}. - Jede Notification hat einen dedupeKey (Ziel + Event + cascadingEventId) → keine Doppel-Benachrichtigung.
WORKAROUND_UPDATED: nur Incident-Agents, keine Ticket-Benachrichtigung.
4. WebSocket-Broadcast
- Nach dem Speichern erhält jede betroffene Entität ein Live-Update (Kanal „activities“).
- Echo-Suppression: der auslösende Client (userId aus payload) erhält kein redundantes Update.
Zuverlässigkeit
| Mechanismus | Wirkung |
|---|---|
| Transactional Outbox | Event und Status-Änderung werden gemeinsam gespeichert oder gar nicht |
| Atomarer Claim | PENDING→PROCESSING in einem atomaren Schritt — kein Event wird parallel doppelt verarbeitet |
| Activity-Dedup | Pro Ziel wird auf cascadingEventId geprüft — Re-Runs erzeugen keine Duplikate |
| Job-Dedup | BullMQ jobId = cascading-{eventId} |
| Retry | 3 Versuche, Exponential-Backoff 1 s / 2 s / 4 s |
| Dead-Letter | Nach 3 Fehlversuchen → status=FAILED + Audit CASCADING_FAILED (ERROR) |
| Crash-Recovery | Beim Start: offene Events > 10 s werden neu eingereiht (PROCESSING → PENDING zurückgesetzt) |
| Stale-Monitoring | CronJob stale_cascading_reminder meldet hängende Resolution-Ketten (Quelle resolved, Kind offen) |
BullMQ-Queue-Konfiguration
Queue: "cascading-events" (Redis)
attempts: 3
backoff: exponential, 1000 ms (1s, 2s, 4s)
concurrency: 5
limiter: max 20 / 1000 ms
removeOnComplete: { count: 500, age: 24h }
removeOnFail: { count: 200 }
Audit
Jede Kaskade wird im Enterprise-Audit-System festgehalten (Domain ENTITY, Kategorie CASCADING, Actor SYSTEM):
CASCADING_PROCESSED(INFO) — erfolgreich verarbeitet, mit affectedCountCASCADING_FAILED(ERROR) — nach endgültigem Fehlschlag (Dead-Letter)
- Problems API — Cascading Close, Impact-Tree als Vorschau
- Incidents API — Status-Transitions, die Kaskaden auslösen
- Entity Linking API — Verlinkungen + Impact-Tree (2-Hop) + BatchResolve
- SLA Management API — pauseOnIncidentLink (SLA-Pause-Kopplung)
- Audit System — CASCADING_PROCESSED / CASCADING_FAILED
- CronJobs API — stale_cascading_reminder
- Notifications — LINKED_INCIDENT_* / LINKED_PROBLEM_*