Zum Inhalt springen
Deutsch

WrapLegacy

Das Envelope-Format des Frameworks tritt nur in Kraft, wenn ein Adapter konfiguriert ist. Bevor du einen Adapter setzt, werden Events roh gespeichert:

{ "kind": "deposited", "amount": 100 }

Nachdem du einen gesetzt hast, werden neue Events eingewickelt:

{ "_v": 1, "_t": "deposited", "_e": { "kind": "deposited", "amount": 100 } }

Das schafft ein Problem, wenn Adapter in ein existierendes Journal eingeführt werden: alten rohen Events fehlt der Envelope, aber der Adapter erwartet Envelopes. Das Lesen eines rohen Events wirft MigrationError.

wrapLegacy überbrückt:

import { wrapLegacy, MigratingAdapter } from 'actor-ts';
class Account extends PersistentActor<...> {
override eventAdapter() {
return wrapLegacy<EventV2>({
legacyVersion: 1,
baseAdapter: new MigratingAdapter<EventV2>({
currentVersion: 2,
chain: [{ from: 1, to: 2, fn: v1ToV2 }],
}),
});
}
}

Was das tut:

  • Bei einem rohen Event (keine _v / _t / _e-Keys): als version: 1 behandeln. Das rohe Event als v1-Payload an den inneren Adapter weitergeben.
  • Bei einem Envelope-Event: unverändert an den inneren Adapter weiterleiten.

So fließen existierende rohe Events durch dieselbe Migrations-Chain wie neue, in Envelopes eingewickelte Events.

interface WrapLegacySettings<E> {
legacyVersion: number; // Version, die rohen Events zugewiesen wird
baseAdapter: EventAdapter<E>;
}
FeldWas
legacyVersionDie Version, als die rohe Events behandelt werden. Typischerweise 1.
baseAdapterDer “echte” Adapter, der den Rest handhabt.

Die Legacy-Version sollte die Version sein, unter der dein Code rohe Events geschrieben hat — normalerweise 1, aber wenn du eine Weile auf v1 warst und die Envelope-Adoption bis v3 übersprungen hast, verwende legacyVersion: 1 und verkette 1→2, 2→3.

Drei Szenarien:

  1. Adapter zu einem existierenden Journal hinzufügen — der häufigste Fall. Einmal einwickeln; ab diesem Punkt sind neue Events Envelopes, alte Events fließen durch Legacy-Wrapping.
  2. Aus einem anderen Framework migrieren — alte Events, die von einem vorherigen System geschrieben wurden, sind jetzt in diesem Journal. Als legacyVersion behandeln und hochmigrieren.
  3. Gemischte Umgebungen während eines schrittweisen Rollouts — einige Events wurden von einer alten Version deines Codes geschrieben (roh); einige von einer neuen Version (Envelope). Einwickeln, um beides zu handhaben.

Nachdem alle rohen Events gelesen wurden (oder durch Snapshots ersetzt sind, die den migrierten State enthalten), könntest du im Prinzip den wrapLegacy-Wrapper droppen. In der Praxis lass ihn drauf — die Kosten sind vernachlässigbar, und das Sicherheitsnetz ist wertvoll.

Die Erkennung ist einfach: eine gespeicherte Payload ohne alle von _v, _t, _e ist Legacy. Das ist in der Praxis eindeutig — echte Events haben praktisch nie zufällig alle drei mit Unterstrich präfixierten Felder.

Wenn dein Journal rohe Events aus mehreren Legacy-Versionen enthält, reicht wrapLegacy allein nicht — es gibt eine legacyVersion für alle rohen Events.

Für Multi-Legacy-Szenarien würdest du einen benutzerdefinierten Adapter schreiben, der die Form des rohen Events inspiziert, um seine Version zu entscheiden:

class MultiLegacyAdapter implements EventAdapter<EventV3> {
upcast(stored: unknown, version: number): EventV3 {
if (version > 0) {
// Envelope-Event mit bekannter Version — Chain handhabt es
return innerChain.upcast(stored, version);
}
// Legacy-Event — Form schnüffeln, um die Version zu bestimmen
const e = stored as any;
if ('cents' in e) return innerChain.upcast(e, 2); // sieht aus wie v2-Form
if ('amount' in e) return innerChain.upcast(e, 1); // sieht aus wie v1-Form
throw new Error('unknown legacy event shape');
}
}

Das ist selten; die meisten Projekte haben eine einzige Pre-Envelope-Form, und wrapLegacy ist ausreichend.

Snapshots haben ihren eigenen Adapter (stateAdapter()), unabhängig von eventAdapter(). Wenn du Legacy-Roh-Snapshots (Pre-Envelope) hast, wickle sie ähnlich ein:

class Account extends PersistentActor<...> {
override snapshotAdapter() {
return wrapLegacy<StateV2>({
legacyVersion: 1,
baseAdapter: new MigratingAdapter<StateV2>({ ... }),
});
}
}

Für die meisten Setups werden Snapshots seltener als Events geschrieben und waren bereits in Envelopes, als Adapter eingeführt wurden — aber doppelte Prüfung, bevor du es annimmst.