How would one go about implementing a replaceable backend (or basically any part or module) so that it can be replaced at configuration/deploy time in Elixir?
My specific situation is a simple web app (in this case using Phoenix but I'm guessing this question applies to other situations as well) where I have a very simple backend using Agent
to keep state but I see a need in the future for being able to switch out the backend more or less dynamically.
I'm guessing both Ecto and Logger do this to some degree but being new to Elixir it's hard to know where to look.
This can be handled through an argument to the supervisor. For example, Ecto's backend supervisor takes an argument called adapter
to specify which kind of database should be used:
# https://github.com/elixir-lang/ecto/blob/364d34bb135e2256fd48327464ada7f7fa2976f9/lib/ecto/repo/backend.ex#L13-L16
def start_link(repo, adapter) do
# Start Ecto, depending on the supplied <repo> and <adapter>
end
You could do the same in your application, probably a single argument to start_link
will be enough – let's call it backend
# my_app/lib/my_app/supervisor.ex
defmodule MyApp.Supervisor do
def start_link(backend) do
# use <backend> as you need to in here,
# it will contain the module that is
# specified in the configuration file.
end
end
Now, you can of course set that argument dynamically when you spin up your application, based on a configuration file:
# my_app/lib/my_app.ex
defmodule MyApp do
use Application
def start(_type, _args) do
MyApp.Supervisor.start_link(backend)
end
# get backend from configuration
def backend do
# ???
end
end
Now, the only piece that is missing is how to get the backend from a configuration file. There's no single answer to that because there are multiple ways of doing this.
You can simply use the existing Mix configuration, but it has the downside that you need to recompile the application every time the configuration changes:
# my_app/config/config.exs
use Mix.Config
config :my_app, backend: MyApp.SpecificBackend
Then adjust your application to read in the specified backend:
# my_app/lib/my_app.ex
defmodule MyApp do
use Application
def start(_type, _args) do
# same as above ...
end
def backend do
Application.get_env(:my_app, :backend)
end
end
You could also implement your own config file. I am not going into detail here, but this is the rough idea:
String.to_existing_atom("Elixir.#{module_name}")
def backend
functionBasically a glorified version of the previous solution. Googling around a bit I found a library called Conform. It looks interesting but I can'T make any promises because I've never used it myself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With