Dynamically load/unload modules in Elixir, something like a plugin system

Viewed 1940

I want to create something like a plugin system: a bunch of modules which get loaded/unloaded dynamically, add I insert/remove them.

Something like this:

# plugin1.eex
defmodule MyApp.Plugins.Plugin1 do

  def plugin_main_func(arg1, arg2) do
    # some stuff
  end
end

# plugin2.eex
defmodule MyApp.Plugins.Plugin2 do

  def plugin_main_func(arg1, arg2) do
    # some stuff 2
  end
end


# plugin3.eex
defmodule MyApp.Plugins.Plugin3 do

  def plugin_main_func(arg1, arg2) do
    # some stuff 3
  end
end

They'll have the same common function - "plugin_main_func" - which implementation will be different for each plugin. And somewhere in my app I'll have this:

plugins = load_plugins()
Enum.each plugins, &(&1.plugin_main_func(1, "fdafdsfds"))

I then be able to add or remove plugins by creating or removing appropriate modules or files. Without having to hard-code/add/remove their names somewhere in an application as a list of strings as well.

How can I achive this?

Update:

Given that a plugin must live in MyApp.Plugins and have a function plugin_main_func, how can I observe a list of plugins they are currently present? I mere want to avoid hard-coding their names somewhere and load or run them only having these 2 conditions which are sufficient for finding all the plugins.

When I find all of them, dynamically or semi-dynamically, I want to somehow be able to call plugin_main_func in each plugin. Without knowing the number of plugins and their exact names. How?

I'm fine with loading them before compilation, not exactly at runtime.

2 Answers

Answering the question, stated in the title: yes, it is possible to dynamically load/unload modules in Elixir. Compile code with e.g. Code.ensure_compiled/1 and purge it with Erlang’s code module:

:code.delete MyApp.Plugins.Plugin1
:code.purge MyApp.Plugins.Plugin1

Answering the whole question: you are doing it wrong. Elixir is a compiled language and you are abusing it. Compilation in a runtime is definitely not what Elixir is good for.

All you need is to keep a list of “loaded” plugins to fake the behavior you are trying to achieve. load would add a module name to a list and unload would remove it from there. And that is it. You do not need to load/unload anything in a runtime: it’s extremely ineffective, dangerous and counter-idiomatic. If you need good runtime plugin support—go for , , or even .


To get the list of loaded modules in a runtime, one might use:

:application.get_key(:my_app, :modules)

You might restrict plugins to the desired namespace (e.g. MyApp.Plugins) and filter the list by that name.

Answer provided by @mudasobwa is really great. All I can do is to give you a hint how to achieve what you want in runtime (by the way - you're trying to use "polymorphism" in the Elixir, nice).

Assuming that these modules are loaded, I wouldn't deal with the compiler to load / remove them. Consider to use any thing that store the state - :ets, GenServer, Agent etc.

I would go with implementing my own GenServer, because it gives flexibility with freedom to customize it as much as I want. Imagine the case where load_plugins() make a call on your GenServer eg.

def load_plugins do
  GenServer.call(MyServer, :load_plugins)
end

And your GenServer starts with the predefined list of plugins and you defines the API to add / remove them in the state. There's definitely no need to load / compile them in the runtime, even if it's possible.

Update

You can always use Code.ensure_loaded? to check whether module is loaded:

iex(1)> Code.ensure_loaded?(MyApp.Repo)
true

iex(2)> Code.ensure_loaded?(MyAppppppppp.Reeeeppo)
false
Related