Persistence
액터는 내부 상태를 보존하고 나중에 복원할 수 있다. 지속성(Persistence)은 액터의 상태를 로컬스토리지나 데이터베이스 같은 영구 저장소에 저장하는 것을 말한다. 복원(Restoration)은 영구 저장소에서 액터의 상태를 복원하는 것을 말한다.
지속성은 브라우저를 다시 로드하는 동안 상태를 유지하는 데 유용하다. 백엔드 어플리케이션에서 지속성은 워크플로우가 여러 요청에 걸쳐있고, 서비스 재시작에도 살아남고, 내결함성, 장기실행프로세스를 할 수 있고, autiable과 traceable하게 해준다.
XState에서는 actor.getPersistedSnapshot()
를 통해서 스냅샷을 가져와 createActor(behavior, { snapshot: restoredState }).start()
를 통해 복원할 수 있다.
const feedbackActor = createActor(feedbackMachine).start();
// Get state to be persisted
const persistedState = feedbackActor.getPersistedSnapshot();
// Persist state
localStorage.setItem("feedback", JSON.stringify(persistedState));
// Restore state
const restoredState = JSON.parse(localStorage.getItem("feedback"));
const restoredFeedbackActor = createActor(feedbackMachine, {
snapshot: restoredState,
}).start();
Persisting state
actor.getPersistedSnapshot()
를 사용해서 보존되는 상태를 얻을 수 있다.
const feedbackActor = createActor(feedbackMachine).start();
// Get state to be persisted
const persistedState = feedbackActor.getPersistedSnapshot();
내부 상태는 머신뿐만 아니라 모든 액터에서 지속될 수 있다. 지속 상태는 액터의 내부 상태를 나타내는 반면, 스냅샷은 액터가 마지막으로 방출한 값을 나타내므로 actor.getSnapshot()
의 스냅샷과 동일하지 않다.
const promiseActor = fromPromise(() => Promise.resolve(42));
// Get the last emitted value
const snapshot = promiseActor.getSnapshot();
console.log(snapshot);
// logs 42
// Get the persisted state
const persistedState = promiseActor.getPersistedSnapshot();
console.log(persistedState);
// logs { status: 'done', data: 42 }
Restoring state
createActor(logic, { snapshot: restoredState })
의 두번째 인수의 state 옵션에 지속 상태를 전달하여 지속 상태로 복원할 수 있다.
// Get persisted state
const restoredState = JSON.parse(localStorage.getItem("feedback"));
// Restore state
const feedbackActor = createActor(feedbackMachine, {
snapshot: restoredState,
});
feedbackActor.start();
머신 액터의 액션의 이미 실행된 것으로 간주되므로 다시 실행되지 않는다. 그러나 호출은 다시 시작되고 스폰된 액터는 재귀적으로 복원된다.
Deep persistence
머신 액터에서 state 지속 & 복원은 깊어서, 모든 호출, 스폰된 액터는 재귀적으로 지속, 복원된다.
const feedbackMachine = createMachine({
// ...
states: {
form: {
invoke: {
id: "form",
src: formMachine,
},
},
},
});
const feedbackActor = createActor(feedbackMachine).start();
// Persist state
const persistedState = feedbackActor.getPersistedSnapshot();
localStorage.setItem("feedback", JSON.stringify(persistedState));
// ...
// Restore state
const restoredState = JSON.parse(localStorage.getItem("feedback"));
const restoredFeedbackActor = createActor(feedbackMachine, {
snapshot: restoredState,
}).start();
// Will restore both the feedbackActor and the invoked form actor at
// their persisted states
Persisting state machine values
상태머신 액터의 유한 상태 값만 유지하려면 machine.resolveState(...)
메서드를 사용할 수 있다.
import { someMachine } from "./someMachine";
const restoredStateValue = localStorage.getItem("someState");
// Assume that this is "pending"
const resolvedState = someMachine.resolveState({
value: restoredStateValue,
// context: { ... }
});
// Restore the actor
const restoredActor = createActor(someMachine, {
snapshot: resolvedState,
});
restoredActor.start();
Event sourcing
상태 지속의 대안으로 이벤트 소싱이란 것이 있는데, 이는 해당 상태를 일으킨 이벤트를 재생하여 액터의 상태를 복원하는 방법이다. 이벤트 소싱은 호환되지 않는 상태가 발생할 가능성이 적고, 동작을 재생할 수 있기 때문에 상태 지속보다 더 안정적일 수 있다.
이벤트 소싱을 구현하는 한 가지 방법은 검사 API를 사용하여 이벤트가 발생할 때 이벤트를 지속한 다음, 이를 재생하여 액터의 상태를 복원하는 것이다.
const events = [];
const someActor = createActor(someMachine, {
// Inspect and persist events
inspect: (inspectionEvent) => {
if (inspectionEvent.type === "@xstate.event") {
const event = inspectionEvent.event;
// Only listen for events sent to the root actor
if (inspectionEvent.actorRef !== someActor) {
return;
}
events.push(event);
}
},
});
someActor.start();
// ...
// Assuming the events are stored somewhere, e.g. in localStorage,
// you can replay them to restore the state of the actor
const restoredActor = createActor(someMachine);
restoredActor.start();
for (const event of events) {
// Replay events
restoredActor.send(event);
}
Caveats
상태를 유지하고 복원할 때 주의해야할 몇 가지 주의 사항이 있다:
- 호환되지않는 상태 : 머신이나 액터로직이 변경되면, 복원된 상태가 새 로직과 호환되지 않을 수 있다.
- 동작 재생 : 이미 실행된 동작은 다시 실행되지 않는다. 이 사용 사례에는 이벤트 소싱이 선호 된다.
- 직렬화 : 상태는 직렬화가 가능해야하며, 이는 JSON 직렬화가 가능해야함을 의미한다. 즉, 함수, 클래스 또는 기타 직렬화할 수 없는 값은 지속할 수 없다.