ifandelse / machina.js

js ex machina - finite state machines in JavaScript
http://machina-js.org/
Other
1.93k stars 147 forks source link

__machina__ fragility? #113

Closed lwhorton closed 6 years ago

lwhorton commented 8 years ago

I have (what I hope) is a rather quick question about machina internals, but I want to give a quick shout-out to how useful machina has proven in building robust, complex applications.

A behavioral fsm is exactly what I was looking for in my current experiment with redux and react. For those familiar, the "flux" architecture is certainly a step in the right direction for wrangling event/data flow, and redux is a great improvement over flux, but both implementations are really missing an opportunity to use machina concepts to even further simplify application complexities.

What I'm currently building is a redux "reducer" that simply defers to a machina instance to handle event states and complex (i.e. business requirement heavy) transitions between those states. For those unfamiliar, a reducer is simply an idempotent function that takes state, does stuff to it, and outputs a new state. That's simple enough - I can literally pass-through all the heavy lifting of sorting state transformations to machina:

let reducer = (state, action) => {
    // the behavrioal fsm does something to mutate state --into--> new state
    fsm.handle(state, action.type, action.payload)
    // mutation is not acceptable in the reducer world, so return
    // a completely new object instead...
    let newState = Object.assign({}, state) 
}

My question is this; when using a behavioral fsm, machina attaches a __machina___ key to the state instance, what is the importance of this machina object? Is it stateful such that future invocations of fsm.handle(stateClient, inputType, args) will depend on a stateClient.__machina__?

I ask because I would (ideally) like to keep state as lean as possible. Virtually any changes at all in the application (new events, callbacks, promise resolves, etc.) will trigger another call to each reducer in the application, and I don't wan't to carry around __machina__ knowledge in my state tree if at all possible. Can I short circuit the behavior by maintaining something like stateClient._currState = stateClient.__machina__.['fsm.0'].state?

george-lin commented 8 years ago

I don't know the answer to your question, but I do know that the namespace will not necessarily be 'fsm.0' if you create multiple fsms from your own extended BehavioralFsm, then that number will increment.

I had to restore previously saved states, so when I am extending BehavioralFsm I just gave it an arbitrary namespace, then I won't have to worry about the name changing on me.

var MyFSM = machina.BehavioralFsm.extend({
    namespace: "mynamespace"
});
ifandelse commented 6 years ago

@lwhorton A thousand apologies for showing up 2 years late (see #146). When I added the BehavioralFsm to machina, I needed a way to "stamp" clients with what machina had done up to that point so that, when given the client state again in the future, the FSM would continue to deal with it as expected. Without this, the FSM wouldn't know that it had seen the client before. I chose to keep this metadata on the client object under __machina__ so that the FSM itself (or machina) wouldn't have to keep track of that state (how would it know when that state could be purged, etc?). Underlying this approach was an overall goal to enable more actor-like scenarios (where state could be persisted for any length, retrieved and acted on later, etc.).

To @george-lin's point, machina gives you a default namespace that it will increment unless you provide your own. If the BehavioralFsm is hierarchical, you'll see multiple namespaces under machina (one for each FSM in the hierarchy). So - if you're only using one FSM (not hierarchical), the backdoor-ish way to keep track of the state would be to hit stateClient.__machina__.['fsm.0'].state like you said. However, the more 'official' way to get that state is to call yourBehavioralFsm.compositeState( stateClient ).