Reputation: 1055
I have an application that should support multiple types of storages. At the moment, I want it to support both S3 and swift.
Now for the question: How do I allow for my application to choose which backend it will use at the load time (as in, the config will specify whether it will be S3 or swift)?
In OOP I would have interface, and could inject a dependency. In this case, the obvious answer was GenServer. But I don't actually need a whole dedicated process (which should also act as a chokepoint for my code).
I considered simply passing the reference to a module as a parameter, but it feels kinda iffy, since technically an incorrect implementation could be passed.
Thus to further specify: How do I go about injecting a specific backend (S3 or swift) into my code based on config (without 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
Upvotes: 1
Views: 403
Reputation: 533
You are on the right way! To use injections by configs, you could specify name of the selected executable module and get its name from config to call it:
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
NOTE 1: You could merge behaviour(App.Filestorage.Behaviour) and "implemention"(App.Filestorage) modules if you want
NOTE 2: You could use a module attribute to specify the adapter from config, but be aware of side effects during deploy, because it will save the exact config that would be during compile time
NOTE 3: If you use adapter specification by function, same as is it in the example, you could even change the selected implementation during runtime, by changing config
More details at the posts: http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/ https://blog.carbonfive.com/lightweight-dependency-injection-in-elixir-without-the-tears/
Upvotes: 2