【问题标题】:How can i change the callback module of a gen_server in erlang? (gen_server:swap_handler)如何更改 erlang 中 gen_server 的回调模块? (gen_server:swap_handler)
【发布时间】:2013-01-22 01:54:15
【问题描述】:

我正在构建一个可以在两种模式下运行的应用程序。沙盒模式和生产模式。

在沙盒模式下,我想在我的 gen_server 中针对数据库进行许多检查:如果表不存在,则创建它;如果列不存在,则添加它;如果列类型不允许我要存储的值然后更改它,等等。

在生产模式下,如果表不存在或列与值的类型不匹配,则会失败,没关系。

所以,为了避免像“case State#state.is_sandbox of true -> ...”这样的繁琐代码, 我想为我的 gen_server 设置两个不同的模块,并且我想在 handle_call 或 handle_info 中更改当前模块。

实际上,我只是想从沙盒转到生产,但我认为如果它以这种方式工作,它可以向后工作。

谢谢。

【问题讨论】:

    标签: erlang erlang-otp gen-server dev-to-production


    【解决方案1】:

    您可以使用gen_fsm,而不是gen_server,这是一个有限状态机,它可以很容易地处理这种情况。您只有多个状态,它们根据状态调用不同模块中的函数。它基本上为您完成所有处理,无需携带显式的state 参数。这基本上是手动实现 FSM。

    【讨论】:

    • 模块参数适合我的情况,因为我的两个模块共享相同的接口,所以代码很短,“清晰”。我会尝试 gen_fsm 进行比较。
    【解决方案2】:

    您可以将module(模块名称)添加到gen_server 中的状态。然后,您将需要 2 个模块 - 沙盒和生产,它们都实现了相同的功能(您可以为其创建一个行为)。

    gen_server 回调将调用module:function,这将是来自沙盒或生产模块的函数。 module 可以在 gen_server 的 init 函数中设置,要更改它,只需在 gen_server 中添加一个新函数即可:

    use_production() ->
        gen_server:cast(production).
    
    ....
    
    handle_cast(production, State) ->
        {noreply, State#state{module = production}).
    

    沙盒模块也是如此。

    带有module 的gen_server 回调示例:

    handle_call(Msg, _From, #state{module = Module} = State) ->
        Module:function(Msq),
        {reply, ok, State}.
    

    function 必须在沙盒和生产模块中实现。

    【讨论】:

    • 使用此解决方案,我的服务器需要在每次调用时检查其 Module 变量的值,然后调用相应的函数。它有效,你不觉得它可能很慢吗?
    • 您唯一需要做的就是从状态#state{module = Module} 中获取模块名称。您不必检查它是沙盒还是生产模块,您只需从该模块调用一个函数。我不认为这很慢 - 调用 Module:function 就像从不同的模块调用普通函数。
    • 来自不同的模块,是的。我相信编译器在知道编译时调用的模块时会优化调用。我会检查这个。使用您的解决方案,我将拥有 3 个模块,调用者和两个仅具有服务器端回调的模块,这非常好。
    【解决方案3】:

    您可以将gen_event 与单个处理程序一起使用,这允许您返回一个swap_handler 元组(请参阅gen_event/handle_*

    此外,您不必在 gen_server 模型中使用 case 语句。如果您的状态包含沙箱变量,您可以通过在标头中绑定沙箱值来为您的回调函数定义不同的子句。例如:

    handle_call(do_stuff, _From, State = #state{sandbox = true}) ->
        do_sandbox_stuff();
    handle_call(do_stuff, _From, State) ->
        do_nonsandbox_stuff().
    

    在此设置中,erlang 会根据沙盒变量的值自动选择要触发的正确子句,而无需定义单独的处理程序或使用 case 语句。以这种方式在函数子句中绑定变量也是提高效率的好习惯(因为变量绑定在函数体之外,绑定过程在调度程序中完成,因此不计入函数的执行时间,而所有匹配都是在一个案例中的函数体内完成的)

    【讨论】:

    • 你认为我可以在我的 gen_server 上调用 gen_event:swap_handler 吗?黑客还是什么? (我不知道子句上的模式匹配更快,谢谢。但我真正的问题是代码:我想要一个包含大量代码的重型模块,以及一个易于阅读和维护的小模块)
    • 没有。 gen_event 实际上支持多个处理程序,但不管进程具有非常不同的内部结构。 gen_server 甚至接受 swap_handler 消息的可能性很小。
    • 不,gen_server 支持swap_handler!它仅支持gen_server:callgen_server:cast,所有其他消息最终都在handle_info 中!检查代码。 gen_server 和 gen_fsm 背后的基本思想是,你给它一个带有所需回调的模块,它描述了该服务器要做什么。即使您进行代码升级,您也使用相同的模块。虽然gen_event:swap_handler 会更改一个模块,但它并不适合该类型的使用。
    【解决方案4】:

    你可以使用os:getenv/1获取模块名称(当然在此之前你必须在不同的环境中设置不同的名称)

    【讨论】:

    • 这需要在每次调用服务器时检查 os/getenv,不是吗?
    • 我实际上的意思是,您可以在开始时使用来自 env 的值创建适当的 gen_server 实例,或者根据 env 值使用 childspec 馈送主管(如果您想在您的应用中生成许多 gen_servers)。跨度>
    • 好的。所以我想要的是在运行时更改模块,而不是在启动时选择好的模块。但是你让我觉得我可以杀死 gen_server 并重新启动另一个。可能是一个解决方案!
    猜你喜欢
    • 2010-12-09
    • 1970-01-01
    • 2018-10-14
    • 2012-09-05
    • 2015-12-15
    • 2011-10-08
    • 2011-08-09
    • 2014-11-09
    • 2019-07-17
    相关资源
    最近更新 更多