Considera che ho implementato un FSM con gen_fsm. Per alcuni eventi in un qualche StateName dovrei scrivere i dati nel database e rispondere all'utente al risultato. Quindi il seguente StateName è rappresentato da una funzione:Principi di OTP. Come separare il codice funzionale e non funzionale nella pratica?
statename(Event, _From, StateData) when Event=save_data->
case my_db_module:write(StateData#state.data) of
ok -> {stop, normal, ok, StateData};
_ -> {reply, database_error, statename, StateData)
end.
dove my_db_module: scrittura è una parte di codice non funzionale applicazione effettiva scrittura del database.
Vedo due problemi principali con questo codice: il primo, un concetto puramente funzionale di FSM è misto a parte di codice non funzionale, questo rende impossibile anche il test dell'unità di FSM. Secondo, un modulo che implementa un FSM ha dipendenza da un'implementazione particolare di my_db_module.
A mio parere, due soluzioni sono possibili:
Implementare my_db_module: write_async come l'invio di un messaggio asincrono ad alcuni database di gestione dei processi, non si risponde in StateName, salvare da in StateData, passare a wait_for_db_answer e attendere il risultato del processo di gestione db come messaggio in handle_info.
statename(Event, From, StateData) when Event=save_data-> my_db_module:write_async(StateData#state.data), NewStateData=StateData#state{from=From}, {next_state,wait_for_db_answer,NewStateData} handle_info({db, Result}, wait_for_db_answer, StateData) -> case Result of ok -> gen_fsm:reply(State#state.from, ok), {stop, normal, ok, State}; _ -> gen_fsm:reply(State#state.from, database_error), {reply, database_error, statename, StateData) end.
vantaggi di tale implementazione è possibilità di inviare messaggi arbitrarie da moduli eunit senza toccare database effettivo. La soluzione soffre di possibili condizioni di gara, se db risponde prima, che FSM cambia stato o un altro processo invia save_data a FSM.
Utilizzare una funzione di callback, scritto durante init/1 in StateData:
init([Callback]) -> {ok, statename, #state{callback=Callback}}. statename(Event, _From, StateData) when Event=save_data-> case StateData#state.callback(StateData#state.data) of ok -> {stop, normal, ok, StateData}; _ -> {reply, database_error, statename, StateData) end.
Questa soluzione non soffre di condizioni di gara, ma se FSM utilizza molti callback davvero travolge il codice. Sebbene il passaggio alla funzione effettiva di callback renda possibile il test delle unità, non risolve il problema della separazione dei codici funzionali.
Non mi sento a mio agio con tutte queste soluzioni. C'è qualche ricetta per gestire questo problema in modo puro OTP/Erlang? Di certo è il mio problema di sottovalutare i principi di OTP e eunit.