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): alsversion: 1behandeln. 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.
Konfiguration
Abschnitt betitelt „Konfiguration“interface WrapLegacySettings<E> { legacyVersion: number; // Version, die rohen Events zugewiesen wird baseAdapter: EventAdapter<E>;}| Feld | Was |
|---|---|
legacyVersion | Die Version, als die rohe Events behandelt werden. Typischerweise 1. |
baseAdapter | Der “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.
Wann verwenden
Abschnitt betitelt „Wann verwenden“Drei Szenarien:
- 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.
- Aus einem anderen Framework migrieren — alte Events, die
von einem vorherigen System geschrieben wurden, sind jetzt in
diesem Journal. Als
legacyVersionbehandeln und hochmigrieren. - 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.
Was als “Legacy” betrachtet wird
Abschnitt betitelt „Was als “Legacy” betrachtet wird“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.
Mehrere Legacy-Versionen
Abschnitt betitelt „Mehrere Legacy-Versionen“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.
Snapshot-Kompatibilität
Abschnitt betitelt „Snapshot-Kompatibilität“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.
Stolperfallen
Abschnitt betitelt „Stolperfallen“Wie geht’s weiter
Abschnitt betitelt „Wie geht’s weiter“- Migration im Überblick — das größere Bild.
- Envelope-Format — wie ein Envelope aussieht.
- DefaultAdapter — für additive Migrationen.
- MigratingAdapter —
für verkettete Transformationen (häufig von
wrapLegacyeingewickelt). - Rezepte — das Rezept für “Ich führe Adapter in ein existierendes System ein.”