如何在 Elixir 的编译时指定模块实现?

How to specify module implementation at compile time in Elixir?

我有一个应用程序应该支持多种类型的存储。目前,我希望它同时支持 S3 和 swift。

现在的问题是:我如何允许我的应用程序选择它将在加载时使用的后端(例如,配置将指定它是否将S3 或 swift)?

在 OOP 中,我会有接口,并且可以注入依赖项。在这种情况下,显而易见的答案是 GenServer。但我实际上并不需要一个完整的专用进程(它也应该作为我的代码的瓶颈)。

我考虑过简单地将对模块的引用作为参数传递,但感觉有点不确定,因为从技术上讲,可能会传递不正确的实现。

因此进一步说明:我如何根据配置(将特定后端(S3 或 swift)注入我的代码没有 Genserver)?

defmodule App.Filestorage do
    @callback store(String.t) :: :ok | :error
end

defmodule App.S3 do
    @behaviour App.Filestorage
    @impl
    def store(path), do: :ok 
end

defmodule App.Swift do
    @behaviour App.Filestorage
    @impl
    def store(path), do: :ok
end

defmodule App.Foo do
    def do_stuff()
        # doing some stuff
        App.Filestorage.store(result_file) # replace this with S3 or Swift
    end
end

你走对了! 要通过配置使用注入,您可以指定所选可执行模块的名称并从配置中获取其名称以调用它:

config:
config :app, :filestorage, App.S3


defmodule App.Filestorage.Behaviour do
    @callback store(String.t) :: :ok | :error
end

defmodule App.Filestorage do
  def adapter(), do: Application.get_env(:app, :filestorage)
  def store(string), to: adapter().store
end

defmodule App.S3 do
    @behaviour App.Filestorage.Behaviour
    @impl true
    def store(path), do: :ok 
end

defmodule App.Swift do
    @behaviour App.Filestorage.Behaviour
    @impl true
    def store(path), do: :ok
end

defmodule App.Foo do
    def do_stuff()
        # doing some stuff
        App.Filestorage.store(result_file) # replace this with S3 or Swift
    end
end

注意 1:如果需要,您可以合并行为(App.Filestorage.Behaviour)和“实现”(App.Filestorage)模块

注意 2:您可以使用模块属性从配置中指定适配器,但要注意部署期间的副作用,因为它会保存编译时的确切配置

注意 3:如果您按功能使用适配器规范,就像在示例中一样,您甚至可以在运行时通过更改 config[=13 来更改所选的实现=]

帖子中有更多详细信息: http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/ https://blog.carbonfive.com/lightweight-dependency-injection-in-elixir-without-the-tears/