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

LegitStack
I’m not a hugely experienced programmer, just a few years. So I’m looking for resources to learn about a topic but I can’t seem to find m...
New
Kielo
Hi, I run a language learning blog and would like to learn how to code so I can create an app to help English speakers learn French. I ...
New
Scoty
Hey, I am currently reading Programming Elixir and I am doing one of the exercises where you should write a solution to the “I’m thinkin...
New
AstonJ
It finally feels like I’ve got some time to catch up on my reading - though I am sure lots of people will be wondering the same: what are...
New
stevensonmt
I’d like to provide my review of the Elixir Course module from Groxio. I have some criticisms but I’d like to start with the positives. ...
New
younes-alouani
I’m studying Phoenix Framework but I want to understand basics first. Which book/mooc explains better the Design of Network-based Softwar...
New
svetarosemond
I’m planning on purchasing Elixir in action second edition, and I was wondering if anyone could tell me based off the first edition, does...
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
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
Twfo326
As a novice dev I’m trying to keep the curriculum as lean as possible. My requirements are modest: build simple CRUD apps with Phoenix...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New

We're in Beta

About us Mission Statement