dorgan

dorgan

Trying to understand how to pick a Behaviour implementation

I’m trying to figure out which is the “best” way to pick a behaviour implementation. I’ve found a couple of examples in other libraries but I haven’t found the rationale behind the design choices. These are the ideas I’ve found so far, and I would like to make sure I understood everything right before continuing to write any code.

Say I have a behaviour FileStorage.Store, and I have multiple implementations for that behaviour: Store.Local, Store.S3, Store.GCS, etc. The module FileStorage is the public API of the feature, and it somehow has to select an implementation.

Reading a bunch of libraries’s source code I’ve found two or three general approaches to this:

The first one is using a private function to get the implementation from the application env:

def action(args) do
  impl = get_impl_from_application_env()
  impl.callback(args)
end

This means that there is a global configuration for the module, something in the lines of:

config :my_app, FileStorage, store: FileStorage.Store.Local

An example of this approach is the Arc library.
This works, but has the limitation that if I need to add a secondary storage, I have to reimplement the public API module with a different config.

The second approach seems to mitigate the limitations of the previous one:

def action(impl, args) do
  impl.callback(args)
end

Now that the implementation is passed to the API function, it’s easy to use different implementations in different places. It enables you to do something like this:

defmodule ModuleA do
  alias FileStorage

  @store FileStorage.Store.Local
  def action(args) do
    FileStorage.save(@store, args)
  end
end

defmodule ModuleB do
  alias FileStorage

  @store FileStorage.Store.S3
  def action(args) do
    FileStorage.save(@store, args)
  end
end

Now the problem is that there’s a lot of repeated code since each module that wants to use FileStorage functions has to pass it the implementation it wants it to use.

I’ve seen libraries use a bit of metaprogramming mixed with a new behaviour to avoid this. The most notable example is Ecto’s Repo. So you’d have something like this:

defmodule FileStorage do
  @callback action(args :: any()) :: :ok | {:error, any()}

  defmacro __using__(opts) do
    quote do
      @behaviour unquote(__MODULE__)

      store = get_store(unquote(opts))
      @store store

      def action(args) do
        FileStorage.action(@store, args)
      end
    end
  end

  def action(impl, args) do
    impl.some_function(args)
  end
end

defmodule ModuleA do
  use FileStorage, store: FileStorage.Store.Local
end

defmodule ModuleB do
  use FileStorage, store: FileStorage.Store.S3
end

defmodule ModuleC do
  def some_function(args) do
    FileStorage.action(FileStorage.Store.GCS, args)
  end
end

Now I can have multiple modules that can configure the way FileStorage is used, both by useing it and by calling it’s functions directly with some specific implementation.

So in short, one can pick an implementation by:

  • Having a private function fetch it from the application’s configuration
  • Having many modules call the api module passing it the implementation to use
  • Use metaprogramming to let other modules configure the way the api is used

Is this how it’s usually done, or I am misinterpreting something?
Thanks!

Most Liked

fuelen

fuelen

I think store configuration for each module should be moved to config too, to have an ability to change it in tests to some mock. Actually, Ecto works this way.

fuelen

fuelen

No, dialyzer won’t help you here.

Where Next?

Popular in Chat/Questions Top

wolfiton
Hi everyone, How can i retrieve the name from a structure like this? %{"id" => "1570", "name" => "Croque Monsieur"} My test loo...
New
KSingh1980
How to run the random query and get the result in JSON format and send it as a response. The query will be in the following form DB Name...
New
maqbool
what books/Resources do you recommend to learn about distributed system(theory)?
New
asfand
I already created an Elixir Phoenix app for learning purpose. In this app students of our collage will create profiles, and will chat wit...
New
Fl4m3Ph03n1x
Background I am trying to recycle myself and improve my knowledge about Phoenix. With 1.7 now out, this seems like a good opportunity. W...
New
zervis
Hello, I’m about to dive into web development. I was thinking about Laravel or Ruby on Rails, but then I found Phoenix. Do you recommen...
New
Fl4m3Ph03n1x
Background I have been reading quite a lot about design and architecture in Elixir and overall FP. My latest incursions took to me the On...
New
phykos
In Ruby, which is very similar to Elixir I do this: def test yield end test do puts("sup there") end Here, the yield keyword will be...
New
lgmfred
Hello, I want to get started with elixir today. Having learnt the basics of Erlang (my first ever language) and seen what it’s can do, I’...
New
QueenSvetlana
My bookstore has Introducing Elixir: Getting Started In Functional Programming and the front cover says the book uses Elixir 1.4. The cur...
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement