Modul 12: Typestate & Event-Sourcing
Typisierbare Zustandsmaschinen fuer eine ganze Domain. Wie Typestate Validez erzwingt und wie Event Sourcing die Historie bewahrt. Paper: Typestate for Rust arXiv 2609.04372 https://arxiv.org/abs/2609.04372
Typestate & Event-Sourcing
Lektion 58 von 5
Warum Zustandsmaschinen typsicher sein wollen
Domain-Driven Design sagt: Unterteile dein System in Aggregate mit klaren
Konsistenzregeln. Eine Bestellung ist neu, wird bezahlt, dann versendet —
nie rueckwaerts, nie willkuerlich. In klassischem OO prueft man solche
Regeln zur Laufzeit: if (!order.canShip()) throw. Typestate verschiebt
solche Pruefungen ins Typsystem: Der Typ selbst verbietet ungueltige
Operationen zur Compile-Zeit.
Lesson 1: Das Problem mit Laufzeit-Pruefungen
Stell dir vor eine Bestellung im Status VERSANDT soll storniert werden. Business-Regel: Stornieren geht nur bis zum Versand. Klassisches OO:
class Order {
status: string;
cancel() {
if (this.status !== "PAID") throw new Error("Zu spaet");
this.status = "CANCELLED";
}
}
new Order({ status: "SHIPPED" }).cancel(); // Laufzeitfehler!
Der Fehler ist zur Laufzeit — Compilierung klappt. Tests koennen ihn finden, aber Tests decken nicht alle Pfade ab.
Lesson 2: Typestate — Zustand als Typ
Jeder Zustand wird ein eigener Typ. Uebergaenge sind Methoden die nur auf bestimmten Typen definierbar sind:
// Rust mit typestate-Crate
pub struct DraftOrder;
pub struct PaidOrder;
pub struct ShippedOrder;
impl DraftOrder {
pub fn place(self, payment: Payment) -> PaidOrder { PaidOrder }
}
impl PaidOrder {
pub fn ship(self, tracking: TrackingId) -> ShippedOrder { ShippedOrder }
pub fn cancel(self) -> CancelledOrder { CancelledOrder }
}
// ShippedOrder hat KEIN cancel() — Compiler verbietet den Aufruf
Typ als Beweis: Wenn das Aggregat den Typ ShippedOrder hat, ist stornieren ein Compile-Error — nicht lauffaehig.
Lesson 3: Typestate trifft Event Sourcing
Event Sourcing (Modul 11) speichert jeden Uebergang als Event:
OrderPlaced, PaymentReceived, Shipped. Typestate erzwingt
Uebergaenge zur Compile-Zeit. Beide ergaenzen sich:
flowchart LR
Command -- Typestate -->|erlaubt?| Event
Event -- append --> EventStore
EventStore -.->|Replay| Aggregat
Event Store ist die single source of truth. Typestate garantiert dass die Write Side nur gueltige Events erzeugt.
Lesson 4: Implementierung in Rust und TypeScript
Rust: typestate-Crate oder manuell mit PhantomData und generischen
Parametern. TypeScript: discriminated unions — jeder Zustand hat
ein Literal-Feld als Discriminator.
// TypeScript: Discriminated Union
type DraftOrder = { kind: "draft"; id: string };
type PaidOrder = { kind: "paid"; id: string; amount: number };
type ShippedOrder = { kind: "shipped"; id: string; tracking: string };
function ship(order: PaidOrder): ShippedOrder { return { kind: "shipped", ... };
function cancel(order: PaidOrder): CancelledOrder { return { kind: "cancelled", ... };
// ShippedOrder hat kein cancel() im Typ — TypeScript verbietet den Aufruf
Lesson 5: Audit plus Garantie
| Aspekt | Typestate | Event Sourcing | Beide |
|---|---|---|---|
| Erzwingt | Compile-Zeit | Laufzeit | Compile + Laufzeit |
| Audit | Nein | Ja — Historie | Ja + sicher |
Typestate ideal fuer Domänen mit festen Regeln und hohen Fehlerkosten.
Compile-Zeit-Sicherheit + Laufzeit-Audit
Typestate macht Business-Regeln unuebersehbar: Aufruf gültig nur wenn der Typ es erlaubt. Event Sourcing speichert jeden Aufruf als Beleg. Zusammen: Correcness by construction + Proof by history.
Praxis-Szenario: Die nicht-stornierbare Bestellung
Ein Team will verhindern dass Bestellungen im Status VERSANDT noch storniert werden. Wie modellierst du das mit Typestate + Event Sourcing damit der Fehler gar nicht kompilierbar ist?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren: