How to use dependency injection pattern in Elixir?

Just to follow up, I’ll give here the same example for this I gave in Slack.

We can define a protocol for our use case - e.g. a repository of users:

defprotocol UserRepository do
  def get(database, id)
end

And then we can define two implementations - one based on a real database and one based on a simple in-memory ETS store:

defmodule Database do
  defstruct [:ecto_repo]

  def new(repo), do: %__MODULE__{ecto_repo: repo}
end

defmodule EtsDatabse do
  defstruct [:table]

  def new(), do: %__MODULE__{table: :ets.new(__MODULE__, [:set, :public])}
end

We can then implement the protocol for those data structures

defimpl UserRepository, for: Database do
  def get(%{ecto_repo: repo}, id) do
    repo.get(User, id)
  end
end

defimpl UserRepository, for; EtsDatabase do
 def get(%{table: table}, id) do
  # assuming {{User, id}, struct} tuples in ets table
  :ets.lookup_element(table, {User, id}, 2)
 end
end

That’s a simple sketch of a protocol-based dependency injection.

7 Likes

I was preparing a message to answer with your example (and the obligatory mention + thank you) but you beat me to it. Core team members are unbelievable :smiley:

It makes perfect sense to me, however I now wonder if I should keep using behaviours for this purpose. This looks nicer and more reliable. What’s the rule of thumb here @michalmuskala ?

1 Like