# State Mutation auf Streams

URL: https://www.mikebild.dev/de/blog/state-mutation-auf-streams/

```javascript
var events = [
  { type: 'ItemAdded', sku: 'A-100', qty: 1 },
  { type: 'ItemAdded', sku: 'B-200', qty: 2 },
  { type: 'QuantityChanged', sku: 'B-200', qty: 3 },
  { type: 'ItemRemoved', sku: 'A-100' }
];

function applyEvent(state, event) {
  var items = {};
  Object.keys(state.items).forEach(function (sku) {
    items[sku] = state.items[sku];
  });

  if (event.type === 'ItemAdded') { items[event.sku] = event.qty; }
  if (event.type === 'QuantityChanged') { items[event.sku] = event.qty; }
  if (event.type === 'ItemRemoved') { delete items[event.sku]; }

  return { items: items };
}

var cart = events.reduce(applyEvent, { items: {} });
// -> { items: { 'B-200': 3 } }
```

Vier Ereignisse, eine Funktion, ein Aufruf von `Array.prototype.reduce` – und am Ende steht der aktuelle Zustand eines Warenkorbs. Bemerkenswert ist, was in diesem Code alles fehlt: kein `cart.addItem()`, kein Setter, keine Stelle, an der jemand ein Objekt anfasst und verändert. Der Warenkorb wird nicht gepflegt, er wird berechnet. Die Liste der Ereignisse ist die eigentliche Wahrheit, der Zustand nur ihre Zusammenfassung.

Das klingt zunächst wie ein Taschenspielertrick, denn natürlich mutiert irgendwo Speicher. Aber die Blickrichtung dreht sich um, und genau diese Drehung finde ich seit einer Weile spannender als jedes neue Framework: Zustand ist hier kein Ding mit Lebenslauf, sondern das Ergebnis einer Faltung über alles, was bisher passiert ist.

## Eine Faltung, kein Setter

In der funktionalen Welt heißt dieses Muster `foldl` – eine Linksfaltung. Man nimmt einen Startwert, läuft von links nach rechts über eine Sequenz und verrechnet in jedem Schritt den bisherigen Zwischenstand mit dem nächsten Element. `reduce` in JavaScript ist exakt das, seit ES5 im Sprachstandard und 2014 in jedem aktuellen Browser vorhanden; nur für die verbliebenen IE8-Installationen braucht es noch einen es5-shim.

Die Signatur der Schrittfunktion ist dabei der interessante Teil:

```javascript
// (state, event) -> state
function applyEvent(state, event) { /* ... */ }
```

Zwei Eigenschaften machen diese Funktion wertvoll. Erstens ist sie deterministisch: Dieselbe Ereignisfolge und derselbe Startwert ergeben immer denselben Zustand. Zweitens ist sie vollständig: Es gibt keinen zweiten Weg, auf dem sich der Zustand ändern könnte, keine Methode, die an der Faltung vorbei ins Objekt greift. Wer wissen will, warum der Warenkorb aussieht, wie er aussieht, liest die Ereignisse. Punkt.

Ob `applyEvent` intern ein frisches Objekt baut oder den übergebenen Zustand direkt beschreibt, ist dagegen fast schon Geschmackssache. Im Beispiel oben kopiere ich die Items, weil ich Zwischenzustände später noch brauche. In einer engen Schleife über hunderttausend Ereignisse darf die Funktion auch mutieren – solange die Mutation in der Faltung eingesperrt bleibt und nach außen nur das Ergebnis sichtbar wird. Die Disziplin steckt nicht in der Unveränderlichkeit der Objekte, sondern in der einen, einzigen Tür, durch die Änderungen gehen.

```mermaid
flowchart LR
  S0[Startzustand<br/>leerer Warenkorb] --> F1((applyEvent))
  E1[ItemAdded<br/>A-100] --> F1
  F1 --> F2((applyEvent))
  E2[ItemAdded<br/>B-200] --> F2
  F2 --> F3((applyEvent))
  E3[QuantityChanged<br/>B-200] --> F3
  F3 --> F4((applyEvent))
  E4[ItemRemoved<br/>A-100] --> F4
  F4 --> S[aktueller Zustand<br/>als Projektion]
```

## Ein kleines Experiment von 2012

Auf die Idee bin ich nicht am Reißbrett gekommen. Im Oktober 2012 habe ich ein winziges Repository namens [statemutatejs](https://github.com/MikeBild/statemutatejs) angelegt. Das README besteht aus einer einzigen Zeile: „simple projection functions, alias foldl, that mutate state on a stream of elements". Mehr Anspruch hatte das Ding nie – ein Experiment, entstanden aus der Frage, wie sich Projektionsfunktionen anfühlen, wenn man sie konsequent als Faltung über einen Strom schreibt statt als Methoden an einem Objekt.

Ich erwähne das nicht, weil daraus eine Bibliothek geworden wäre, die irgendjemand einsetzen sollte. Sondern weil mir beim Herumprobieren etwas aufgefallen ist, das ich damals nur halb verstanden habe: Sobald die Schrittfunktion vom Container getrennt ist, wird austauschbar, worüber gefaltet wird. Ein Array, ein Node-Stream, eine Observable-Sequenz – die Projektionsfunktion bleibt wörtlich dieselbe. Das eine Wort „foldl" im README war im Rückblick der ganze Punkt des Experiments.

Zwei Jahre später sehe ich dasselbe Muster an immer mehr Stellen wieder, und es lohnt sich, die Varianten einmal nebeneinanderzulegen.

## Dasselbe Muster auf Node-Streams

Seit Node 0.10 gibt es die überarbeitete Stream-Schnittstelle – streams2 – und mit ihr den `objectMode`, in dem ein Stream keine Buffer, sondern beliebige Objekte transportiert. Damit löst sich die Faltung vom Array: Die Ereignisse müssen nicht mehr vollständig im Speicher liegen, sie dürfen eintreffen, wann immer sie wollen.

```javascript
var stream = require('stream');
var util = require('util');

function StateFold(applyFn, initialState) {
  stream.Writable.call(this, { objectMode: true });
  this.state = initialState;
  this.applyFn = applyFn;
}
util.inherits(StateFold, stream.Writable);

StateFold.prototype._write = function (event, encoding, done) {
  this.state = this.applyFn(this.state, event);
  done();
};

// same projection function as before, no changes needed
var fold = new StateFold(applyEvent, { items: {} });
eventSource.pipe(fold);

fold.on('finish', function () {
  console.log(fold.state); // -> { items: { 'B-200': 3 } }
});
```

Das ist bewusst unspektakulär: ein `Writable` im Objektmodus, das pro Ereignis einmal die Schrittfunktion aufruft und den Zwischenstand behält. `applyEvent` ist unverändert die Funktion aus dem ersten Beispiel. Ob `eventSource` eine Datei mit zeilenweise serialisierten JSON-Ereignissen ist, ein TCP-Socket oder ein Parser, der Logzeilen in Objekte übersetzt, spielt für die Faltung keine Rolle. Die Backpressure-Mechanik von streams2 sorgt nebenbei dafür, dass ein langsamer Konsument die Quelle bremst, statt Ereignisse im Speicher zu stapeln.

In einem Projekt Anfang des Jahres haben wir genau so eine Auswertung über mehrere Gigabyte an Protokolldaten gebaut: Ereignisse aus Dateien lesen, durch eine Handvoll Projektionsfunktionen falten, fertig. Kein Zwischenspeicher, keine Datenbank für die Auswertung selbst – nur Streams und Faltungen. Der Code war so klein, dass die Diskussion im Review länger dauerte als das Schreiben.

## scan() macht die Zwischenzustände sichtbar

`reduce` liefert am Ende genau einen Wert. Auf einem endlosen Strom – Benutzereingaben, Sensordaten, Kursdaten – gibt es aber kein Ende und damit auch kein einzelnes Ergebnis. Die Reactive Extensions für JavaScript, aktuell in Version 2.x, haben dafür den Operator `scan`: dieselbe Faltung, nur dass jeder Zwischenzustand als Element eines neuen Stroms nach außen gereicht wird.

```javascript
var cartStates = cartEvents
  .scan({ items: {} }, applyEvent);

cartStates.subscribe(function (state) {
  renderCart(state); // every intermediate state, as it happens
});
```

Wieder dieselbe Projektionsfunktion, kein Zeichen geändert. Bacon.js bietet mit `scan` denselben Operator an, falls einem der Rx-Stil nicht liegt. Interessant wird das im Browser: Wenn jede Benutzerinteraktion ein Ereignis ist und der Anwendungszustand ein `scan` darüber, dann ist die Oberfläche zu jedem Zeitpunkt eine Funktion des bisherigen Ereignisstroms. Ich habe das in einem Prototyp für ein Dashboard ausprobiert, und die ungewohnteste Erfahrung war dabei gar nicht der Code. Es war das Debugging: Statt im Debugger nach dem Objekt zu suchen, das gerade kaputtging, habe ich die letzten zwanzig Ereignisse ausgegeben und den Fehler in der Projektionsfunktion gefunden. Der kaputte Zustand war kein Rätsel mehr, sondern das nachvollziehbare Ergebnis einer konkreten Ereignisfolge.

## Undo, Replay, Nachvollziehbarkeit – Nebenprodukte

Und damit zum eigentlichen Grund, warum mich dieses Muster nicht mehr loslässt. In klassisch mutierendem Code sind bestimmte Anforderungen teuer, weil der alte Zustand beim Ändern zerstört wird. Wer Undo will, muss Schnappschüsse anlegen oder inverse Operationen pflegen. Wer wissen will, wie es zu einem Zustand kam, braucht Protokollierung, die jemand vorher eingebaut haben muss. In der Faltungswelt fallen diese Anforderungen einfach ab:

```javascript
function stateAt(events, count) {
  return events.slice(0, count).reduce(applyEvent, { items: {} });
}

var current = stateAt(events, events.length);     // state now
var undone = stateAt(events, events.length - 1);  // state before the last event
var initial = stateAt(events, 0);                 // start over
```

Undo ist eine Faltung über alle Ereignisse bis auf das letzte. Replay ist dieselbe Faltung noch einmal, gern auch schrittweise als Zeitraffer. Und die Nachvollziehbarkeit steckt in der Ereignisliste selbst – sie ist das Protokoll, das man sonst mühsam nachrüstet.

```mermaid
flowchart TB
  L[Ereignisliste<br/>e1, e2, e3, e4] --> A[Faltung über alle<br/>= aktueller Zustand]
  L --> B[Faltung über e1 bis e3<br/>= Undo]
  L --> C[Faltung Schritt für Schritt<br/>= Replay / Zeitreise]
  L --> D[Liste selbst lesen<br/>= Audit-Protokoll]
```

Ein paar Dinge bekommt man auf diesem Weg praktisch geschenkt:

- Undo und Redo, ohne inverse Operationen zu schreiben
- Reproduktion von Fehlern, indem man die aufgezeichnete Ereignisfolge erneut faltet
- neue Sichten auf alte Daten durch eine zweite Projektionsfunktion über dieselben Ereignisse
- Tests, die nur aus einer Ereignisliste und einem erwarteten Endzustand bestehen

Der letzte Punkt ist im Alltag der unterschätzteste. Ein Test für `applyEvent` braucht keinen Mock, keine Datenbank, kein Setup – Eingabeliste rein, Endzustand vergleichen, fertig.

## Vom Spielzeug zur Architekturfrage

Neu ist die Idee natürlich nicht. Martin Fowler hat das Muster schon 2005 unter dem Namen Event Sourcing beschrieben: Der Anwendungszustand wird nicht direkt gespeichert, sondern aus einer Folge von Ereignissen rekonstruiert. Greg Young hat es in seinen Arbeiten zu CQRS weitergedacht – die Trennung von Schreibseite, die Ereignisse erzeugt, und Leseseite, die Projektionen darüber baut. Was mir mein kleines JavaScript-Experiment gezeigt hat, ist vor allem, wie kurz der Weg vom `reduce`-Einzeiler zu dieser Architekturfrage ist: Sobald man Ereignisse als die Wahrheit behandelt und Zustand als Projektion, hat man Event Sourcing im Kleinen bereits gebaut – nur eben im Speicher statt in einer Datenbank.

Ich will die Idee dabei nicht größer reden, als sie ist. Wer die Faltung zur Systemarchitektur macht, kauft sich echte Probleme ein. Die Ereignisliste wächst unbegrenzt, also braucht es irgendwann Schnappschüsse, damit nicht jeder Start Millionen Ereignisse durchfaltet. Die Struktur der Ereignisse ändert sich mit der Anwendung, alte Ereignisse müssen aber lesbar bleiben – diese Schema-Evolution begleitet einen dauerhaft und ist alles andere als eine Fußnote. Und für einen simplen Verwaltungsdialog, dessen Historie niemanden interessiert, ist ein direktes Update schlicht das ehrlichere Werkzeug. Ereignisse aufzuheben lohnt sich dort, wo die Geschichte fachlich etwas bedeutet: Bestellungen, Konten, Verträge, Messreihen.

Deshalb halte ich es im Moment so: im Kleinen konsequent, im Großen vorsichtig. Innerhalb eines Moduls Ereignisse zu falten kostet fast nichts und zwingt zu einer sauberen Trennung von „was ist passiert" und „was folgt daraus". Ob daraus ein persistenter Ereignisspeicher fürs ganze System werden soll, ist eine deutlich größere Entscheidung, die man nicht nebenbei im Refactoring trifft.

## Was ich daraus mitnehme

Das README von statemutatejs war eine Zeile lang, und trotzdem hat mich das Experiment mehr gelehrt als manches Framework-Handbuch. Die Lehre ist unscheinbar: Wenn Änderungen als Werte vorliegen – als Ereignisse, die man aufheben, ansehen und erneut abspielen kann –, dann ist der aktuelle Zustand nur noch eine von vielen möglichen Auswertungen. `reduce` über ein Array, ein `Writable` im Objektmodus, `scan` in RxJS oder Bacon.js: viermal dieselbe Faltung, viermal dieselbe Projektionsfunktion.

Seitdem stelle ich in Code-Reviews eine Frage öfter als früher: Wo entsteht dieser Zustand, und könnten wir die Änderungen stattdessen als Ereignisse festhalten? Meistens lautet die Antwort „lohnt hier nicht", und das ist in Ordnung. Aber in den Fällen, in denen jemand gerade eine Undo-Funktion, ein Audit-Log oder den Abgleich zweier Datenstände von Hand baut, zeigt die Frage fast immer denselben Ausweg: Die gewünschten Eigenschaften stecken schon in den Ereignissen. Man muss sie nur falten.

## Weiterführende Quellen

- [MikeBild/statemutatejs](https://github.com/MikeBild/statemutatejs) – das erwähnte Experiment von Oktober 2012
- [Martin Fowler: Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) – die Musterbeschreibung von 2005
- [Node.js 0.10 Stream-API](https://nodejs.org/docs/latest-v0.10.x/api/stream.html) – streams2 inklusive objectMode
- [MDN: Array.prototype.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) – die Faltung im Sprachstandard
- [Reactive-Extensions/RxJS](https://github.com/Reactive-Extensions/RxJS) – RxJS mit dem scan-Operator
