gen_fsmでFSMを実装していると考えてください。いくつかのStateNameのいくつかのイベントの場合、データベースにデータを書き込み、結果をユーザーに返信する必要があります。したがって、次のStateNameは関数で表されます。
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.
ここで、my_db_module:writeは、実際のデータベース書き込みを実装する非機能コードの一部です。
このコードには2つの大きな問題があります。1つは、FSMの純粋関数型の概念が非機能型コードの一部と混在しているため、FSMの単体テストも不可能です。次に、FSMを実装するモジュールは、my_db_moduleの特定の実装に依存しています。
私の意見では、2つの解決策が可能です。
my_db_module:write_asyncを、非同期メッセージをプロセス処理データベースに送信するように実装し、statenameで応答せず、FromをStateDataに保存し、wait_for_db_answerに切り替えて、db管理プロセスからの結果を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.
このような実装の利点は、実際のデータベースに触れることなく、eunitモジュールから任意のメッセージを送信できることです。ソリューションは、dbが以前に応答した場合、FSMが状態を変更するか、別のプロセスがsave_dataをFSMに送信する可能性のある競合状態に悩まされます。
StateDataのinit/1中に記述されたコールバック関数を使用します。
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.
このソリューションは競合状態に悩まされることはありませんが、FSMが多くのコールバックを使用する場合、コードを実際に圧倒します。実際の関数コールバックに変更すると単体テストが可能になりますが、関数型コードの分離の問題は解決されません。
私はこのすべての解決策に満足しているわけではありません。この問題を純粋なOTP/Erlangの方法で処理するためのレシピはありますか?それは、OTPとeunitの原則を過小評価するという私の問題かもしれません。