Zum Inhalt springen
Deutsch

In-Memory-Journal

InMemoryJournal speichert jedes Event im Prozess-Speicher. Es ist der Default, wenn du ein System ohne konfigurierte Persistenz erstellst — schnell, null Setup, perfekt für Tests und Dev.

import { InMemoryJournal, InMemorySnapshotStore, PersistenceExtensionId, ActorSystem } from 'actor-ts';
const system = ActorSystem.create('demo');
system.extension(PersistenceExtensionId).configure({
journal: new InMemoryJournal(),
snapshotStore: new InMemorySnapshotStore(),
});
// Jetzt schreiben `PersistentActor`s in diesem System in das In-Memory-Journal.

Es ist außerdem die Referenz-Semantik, der jede andere Journal-Implementierung entsprechen muss — Append-only, monotone Sequenznummern, Exactly-once-Delivery an Subscriber, Concurrency-Check auf erwarteter Sequenz.

Der Journal-Vertrag:

interface Journal {
append<E>(pid: string, events: E[], expectedSeq: number, tags?: string[]): Promise<PersistentEvent<E>[]>;
read<E>(pid: string, fromSeq: number, toSeq?: number): Promise<PersistentEvent<E>[]>;
highestSeq(pid: string): Promise<number>;
deleteEventsUpTo(pid: string, seqNr: number): Promise<void>;
persistenceIds(): AsyncIterable<string>;
readonly events: JournalEventBus;
}

InMemoryJournal implementiert all das intern mit einer Map<string, PersistentEvent[]>. Operationen sind:

  • append — Events auf das Array des Streams pushen. Concurrency-Check auf expectedSeq (wirft JournalConcurrencyError, wenn der aktuelle Head nicht passt).
  • read — das Array nach Sequenznummern-Bereich slicen.
  • highestSeq — die seqNr des letzten Eintrags lesen (oder 0).
  • deleteEventsUpTo — das Präfix abschneiden.
  • persistenceIds — über die Keys der Map iterieren.

Jedes append publiziert das Event außerdem in den In-Process- JournalEventBus — sodass Push-basierte Queries (siehe Push-basierte Query) in Echtzeit ohne Polling funktionieren.

Drei legitime Fälle:

  1. Tests — schnell, kein IO, kein Cleanup zwischen Tests nötig. Ein frisches System hochziehen, Assertions machen, abreißen.
  2. Entwicklung — wenn du dich beim lokalen Iterieren nicht mit einer SQLite-Datei herumschlagen willst.
  3. Wegwerf-Demos — wenn die Daten nicht überleben sollen.

Für alles, was über Neustarts hinweg persistieren muss, wechsle zu SQLite oder Cassandra.

import { describe, it, beforeEach, afterEach } from 'bun:test';
import { TestKit, InMemoryJournal, InMemorySnapshotStore, PersistenceExtensionId } from 'actor-ts';
describe('OrderActor', () => {
let tk: TestKit;
beforeEach(() => {
tk = TestKit.create();
tk.system.extension(PersistenceExtensionId).configure({
journal: new InMemoryJournal(),
snapshotStore: new InMemorySnapshotStore(),
});
});
afterEach(async () => { await tk.shutdown(); });
it('replays events on restart', async () => {
const probe = tk.createTestProbe();
// Anlegen + ein paar Commands senden
let order = tk.system.spawnAnonymous(Props.create(() => new OrderActor('order-1')));
order.tell({ kind: 'place', sku: 'book-1' });
order.tell({ kind: 'place', sku: 'book-2' });
await probe.expectMsg({ kind: 'placed', sku: 'book-2' });
// Stoppen + neu spawnen — gleiche Persistence-ID
await tk.system.stop(order);
order = tk.system.spawnAnonymous(Props.create(() => new OrderActor('order-1')));
order.tell({ kind: 'view', replyTo: probe });
// Wiederhergestellter State sollte beide platzierten Events widerspiegeln
await probe.expectMsg({ items: ['book-1', 'book-2'] });
});
});

Ein frisches InMemoryJournal pro Test (über TestKit pro Test) macht jeden Fall isoliert — kein State-Leakage zwischen Tests.

// Vorher (Test / Dev):
system.extension(PersistenceExtensionId).configure({
journal: new InMemoryJournal(),
});
// Nachher (Produktion):
import { SqliteJournal } from 'actor-ts';
system.extension(PersistenceExtensionId).configure({
journal: new SqliteJournal({ path: '/var/lib/my-app/events.db' }),
});

Keine Code-Änderungen innerhalb der Actors — das Journal- Interface ist dasselbe. Tausche einfach die Implementierung aus.

Deshalb sagen wir “verwende In-Memory für Tests und Dev” — der Produktions-Switch ist eine Zeile, und die Test-Suite läuft weiter gegen schnelle In-Memory-Journals.