Abo-Bugs sind Zustands-Bugs. Ein Kunde behält nach der Kündigung den Zugriff. Ein überfälliges Konto zahlt und bleibt gesperrt. Eine Testphase endet für jemanden ohne Karte, und keine Regel sagt, was dann passiert. Jeder dieser Fälle ist ein Übergang, den niemand gezeichnet hat.
Ein Zustandsautomaten-Diagramm, meist einfach Zustandsdiagramm genannt, listet jeden Zustand auf, in dem ein Objekt sein kann, und jedes Ereignis, das es in einen anderen bringt. Fehlende Übergänge fallen im Review auf statt in Support-Tickets. Dieser Beitrag baut eines für ein SaaS-Abo auf, mit den Status, die Stripe Billing dokumentiert, damit du das Modell mit einem echten System vergleichen kannst, und erklärt die Notation, sobald sie auftaucht.
Wann sich ein Zustandsautomat lohnt
Zeichne einen, wenn ein einzelnes Objekt einen Lebenszyklus mit Regeln hat: eine Bestellung, ein Abo, eine Zahlung, ein Support-Ticket, ein Deployment. Die Warnzeichen kennst du:
- Eine
status-Spalte mit mehr als drei Werten und Code, der sie an vielen Stellen prüft. - Boolesche Flags wie
is_canceledundis_paused, die sich widersprechen können. - Diskussionen darüber, ob etwas aus einem bestimmten Status heraus „passieren kann“.
Für ein Gespräch zwischen mehreren Beteiligten ist ein Zustandsdiagramm das falsche Bild; dort zeigt ein Sequenzdiagramm die Reihenfolge der Nachrichten. Für einen Geschäftsprozess mit vielen Personen und Übergaben passt ein BPMN-Diagramm besser.
Die Notation
| Element | Notation | Bedeutung |
|---|---|---|
| Zustand | Rechteck mit abgerundeten Ecken | Eine Bedingung, in der das Objekt bleibt, bis ein Ereignis eintrifft |
| Anfangszustand | Kleiner gefüllter Kreis | Wo der Automat beginnt |
| Endzustand | Gefüllter Kreis in einem Ring | Der Automat ist fertig |
| Übergang | Pfeil mit der Beschriftung event [guard] / action |
Wechsel, wenn das Ereignis eintritt und der Guard wahr ist |
| Auswahl | Raute | Guards an den ausgehenden Pfeilen bestimmen das Ziel |
| Zusammengesetzter Zustand | Ein Zustand, der Zustände enthält | Regeln, die für alles darin gelten |
| Internes Verhalten | entry /, exit /, do / |
Läuft beim Eintreten, beim Verlassen oder während des Zustands |
Jeder Teil einer Übergangsbeschriftung ist optional. invoice paid ist für sich ein Ereignis, [setting: unpaid] für sich ein Guard, und cancel / revoke access ist ein Ereignis mit Aktion. Syntax und Symbole stammen aus der UML-2.5.1-Spezifikation.
Die Abo-Status
Das Beispiel nutzt die Status aus Stripes Abo-Übersicht, mit automatischen Kartenzahlungen:
| Status | Was er bedeutet | Zugriff |
|---|---|---|
incomplete |
Die erste Zahlung ist noch nicht erfolgt; der Kunde hat 23 Stunden | Nein |
incomplete_expired |
Die erste Zahlung ist nie gelungen; nichts wird berechnet | Nein |
trialing |
In einer Testphase | Ja |
active |
In gutem Zustand | Ja |
past_due |
Die Zahlung der letzten Rechnung ist fehlgeschlagen oder wurde nicht versucht | Deine Entscheidung |
unpaid |
Die Wiederholungen sind erschöpft, und deine Einstellungen haben dieses Ergebnis gewählt | Nein |
paused |
Die Testphase endete ohne Zahlungsmethode, und Pausieren wurde gewählt | Nein |
canceled |
Beendet; Stripe beschreibt es als Endzustand | Nein |
Viele Produkte lassen den Zugriff während past_due bestehen, solange Zahlungswiederholungen laufen, und dieses Beispiel tut das auch. Schreib diese Entscheidung ins Diagramm; sie ist die, die gern vergessen wird.
Die Übergänge aufbauen
Nimm jeden Zustand und frag, welche Ereignisse dort eintreten können.
- Start.
subscribe [trial]führt zutrialing;subscribe [no trial]führt zuincomplete. - Erste Zahlung. Aus
incompleteführtinvoice paidzuactive. Vergehen 23 Stunden ohne Zahlung, wechselt das Abo zuincomplete_expired, und Stripe storniert die Rechnung. Dieser Zustand ist endgültig: Für einen neuen Versuch startet der Kunde ein neues Abo. - Ende der Testphase.
trial endsführt zu einer Auswahl.[payment succeeds]führt zuactive;[no payment method]führt in diesem Beispiel zupaused. Stripe lässt dich wählen, was in diesem Fall passiert: eine Rechnung erstellen (die Voreinstellung), kündigen oder pausieren. Zeichne die Einstellung, die du tatsächlich nutzt, nicht alle drei. - Verlängerungen. Aus
activeführtrenewal payment failszupast_due, undinvoice paidbringt es zurück. - Wiederholungen erschöpft. Noch eine Auswahl: Je nach deinen Einstellungen wechselt das Abo zu
unpaidodercanceledoder bleibtpast_due. Ausunpaidbringt das Bezahlen der Rechnung es zurück zuactive. - Ende.
cancelbringtactivezucanceled. Kündigung zum Periodenende ist kein eigener Zustand: Das Abo bleibt mit einem Flagactive, bis die Periode endet, und wechselt dann zucanceled.
Ein zusammengesetzter Zustand für den Zugriff
Hier gewähren drei Zustände Zugriff: trialing, active und past_due. Zeichnest du sie in einen zusammengesetzten Zustand, Access granted, schreibst du die Regel nur einmal: entry / grant access und exit / revoke access. Jeder Übergang, der den zusammengesetzten Zustand verlässt, entzieht den Zugriff, egal aus welchem Zustand er startet, sodass kein Pfad es vergessen kann. Ein Wechsel zwischen Zuständen innerhalb, etwa von trialing zu active, verlässt ihn nicht und führt diese Aktionen nicht aus.
Diagramminhalt
- Access granted
- trialing
- active
- past_due
- paused
- incomplete
- incomplete_expired
- unpaid
- canceled
- subscribe [trial]
- subscribe [no trial]
- invoice paid
- after 23 h / void invoice
- trial ends
- [payment succeeds]
- [no payment method]
- resume [payment succeeds]
- renewal payment fails
- retries exhausted
- [setting: unpaid]
- [setting: cancel]
- cancel
So liest du es:
- Oben. Der Startpunkt teilt sich nach der Testphase:
trialingim zusammengesetzten Zustand oderincompleteaußerhalb. Ausincompleteführt die Zahlung zuactive, und 23 Stunden ohne Zahlung führen zuincomplete_expiredund zum Endzustand rechts. - Mitte, Access granted.
trial endserreicht eine Auswahl:active, wenn die Zahlung gelingt,pausedlinks, wenn es keine Zahlungsmethode gibt. Eine fehlgeschlagene Verlängerung bringtactivezupast_due, und eine Zahlung bringt es zurück. - Unten. Sind die Wiederholungen erschöpft, schickt eine zweite Auswahl das Abo zu
unpaidodercanceled.unpaidkann sich noch zurück zuactivebezahlen;canceledendet im Endzustand. - Kanten in den Rahmen hinein gewähren Zugriff, Kanten heraus entziehen ihn.
Vom Diagramm zum Code
Das Diagramm bildet auf eine Übergangstabelle ab. Alles, was nicht in der Tabelle steht, wird abgelehnt:
type Status = 'incomplete' | 'incomplete_expired' | 'trialing' | 'active'
| 'past_due' | 'unpaid' | 'paused' | 'canceled';
const transitions: Record<Status, Partial<Record<string, Status>>> = {
incomplete: { invoice_paid: 'active', expired: 'incomplete_expired' },
trialing: { trial_ended_paid: 'active', trial_ended_no_method: 'paused' },
active: { payment_failed: 'past_due', cancel: 'canceled' },
past_due: { invoice_paid: 'active', retries_exhausted: 'unpaid' },
unpaid: { invoice_paid: 'active' },
paused: { resumed: 'active' },
incomplete_expired: {},
canceled: {},
};
function next(status: Status, event: string): Status {
const target = transitions[status][event];
if (!target) throw new Error(`${event} is not allowed in ${status}`);
return target;
}
Kommen die Ereignisse aus den Webhooks eines Zahlungsanbieters, verlass dich nicht auf die Reihenfolge. Stripes Webhook-Dokumentation sagt, dass es nicht garantiert, dass Ereignisse in der Reihenfolge ankommen, in der sie erzeugt wurden. Nimm jedes Ereignis als Anlass, den aktuellen Status des Abos abzurufen, und wende den Übergang darauf an.
Häufige Fehler
- Fehlende Ereignisse. Prüfe für jeden Zustand jedes Ereignis. „Was passiert, wenn der Kunde während
past_duekündigt?“ ist eine Frage, die das Diagramm beantworten sollte. - Flags als Zustände gezeichnet. Kündigung zum Periodenende, ein Rabatt oder eine Seat-Anzahl sind Eigenschaften eines Zustands, keine Zustände.
- Aktionen als Zustände gezeichnet. „Mahn-E-Mail senden“ ist eine Aktion an einem Übergang oder ein
entry-Verhalten, kein Ort, an dem das Abo wartet. - Kein Endzustand. Zeig, wo der Lebenszyklus endet, damit niemand Code schreibt, der ein gekündigtes Abo wiederbelebt.
- Guards, die sich überlappen oder Lücken lassen. Die Guards an einer Auswahl müssen jeden Fall genau einmal abdecken; füge bei Bedarf
[else]hinzu. - Zwei Lebenszyklen in einem Diagramm. Das Abo und seine Rechnungen haben getrennte Zustandsautomaten. Verbinde sie über Ereignisse, statt sie zu verschmelzen.
Halte das Diagramm neben dem Code, der es umsetzt, und prüfe beides im selben Pull Request, wie in Architekturdiagramme, die nicht veralten beschrieben.
In Archboard nachbauen
- Melde dich mit Google oder GitHub an und leg ein Diagramm an.
- Füge aus dem UML-Pack für jeden Status einen Zustand hinzu.
- Füge aus dem Basis-Pack einen Rahmen namens Access granted um
trialing,activeundpast_duehinzu. - Füge aus dem Basis-Pack eine kleine, gefüllte Ellipse für den Anfangszustand hinzu, eine Raute für jede Auswahl und ein Symbol mit einem Kreis mit Punkt für jeden Endzustand.
- Verbinde die Zustände mit Kanten, die mit
event [guard] / actionbeschriftet sind. - Füge eine UML-Notiz mit den Ein- und Austrittsaktionen des zusammengesetzten Zustands hinzu.
Das Beispiel nutzt 14 Diagramm-Nodes und bleibt damit im Free-Plan. Mit einem über MCP verbundenen KI-Agenten kannst du dein Status-Enum einfügen und ihn die Zustände entwerfen lassen; jeden Übergang prüfst du dann selbst.
FAQ
Was ist der Unterschied zwischen einem Zustandsdiagramm und einem Flussdiagramm? Ein Flussdiagramm zeigt die Schritte eines Prozesses in ihrer Reihenfolge. Ein Zustandsdiagramm zeigt die Bedingungen, in denen ein Objekt sein kann, und welche Ereignisse es zwischen ihnen bewegen; es beantwortet also, was erlaubt ist, nicht, was als Nächstes passiert.
Sollte jeder Wert meiner Status-Spalte ein Zustand sein? Jeder Wert, der das Verhalten ändert, schon. Verhalten sich zwei Werte in jedem Fall gleich, führe sie zusammen; verbirgt ein Wert zwei Verhaltensweisen, teile ihn.
Wo gehören Webhooks in einen Zustandsautomaten? Sie sind Ereignisse. Prüfe sie, ruf den aktuellen Status beim Anbieter ab und wende den Übergang an, denn Ereignisse können in falscher Reihenfolge oder mehrfach ankommen.
Brauche ich einen zusammengesetzten Zustand? Nutze einen, wenn mehrere Zustände eine Regel teilen, etwa Zugriff oder Abrechnung. Er macht aus einer Regel, die du an vielen Pfeilen wiederholen würdest, eine Eintritts- und eine Austrittsaktion.
Wie detailliert sollten Übergangsbeschriftungen sein? Benenne das Ereignis in fachlichen Begriffen und füge einen Guard nur hinzu, wo zwei Pfeile beim selben Ereignis abgehen. Aktionen, die für die Korrektheit zählen, etwa das Gewähren von Zugriff, gehören ins Diagramm.