CharlesO

CharlesO

A better Module interaction pattern?

I’m looking to improve this pattern.
It is a pattern I use in a lot of my projects.

defmodule Handler do
    def handle_request(data) do
        response = apply(@some_module_from_config, :parse, [data])

        # do_something_with_response(response)
        ...
    end
end

I then have customer specific implementations of :parse in separate modules, for example

defmodule CustomerA do
    def parse(data) do
        # specific parsing of this data for Customer-A
    end
end

defmodule CustomerB do
    def parse(data) do
         # specific parsing of this data for Customer-B
    end
end

This works, but can it be improved?

Is there a benefit for example to have a @behaviour in Handler which defines :parse and other functions

Then each Customer module no implements use Handler

Please are their better patterns for doing this?

Thanks.

Marked As Solved

D4no0

D4no0

Nope, all you need is something like this:

defmodule ParseBehavior do
  # Obviously replace any with your data types
  @callback parse(data :: any) :: any
end

defmodule CustomerA do
  @behaviour ParseBehavior 
end

If you don’t implement the specified callbacks you will get a compilation warning.

You can also have the variant with the default implementation:

defmodule ParseBehavior do
  @callback parse(data :: any) :: any

  defmacro __using__(_opts) do
    quote do
      @behaviour ParseBehavior
      
      def parse(data) do
        # This will be the default implementation if not overriden
      end

     defoverridable parse: 1
    end
  end
end

defmodule CustomerA do
   use ParseBehavior

  # This definition will override the default definition
  def parse(data) do
   
  end
end

I wouldn’t recommend using the variant with default implementation unless that is strictly required, as it makes the code harder to read.

Also Liked

al2o3cr

al2o3cr

The major benefit is compile-time checking:

  • if CustomerA says @behaviour Handler but doesn’t supply callbacks with the correct arities, the compiler will complain
  • (sometimes) Dialyzer will be able to prove that a function’s implementation fails to match the behaviour’s spec

As a side bonus, libraries like mox can use that same behaviour for their definitions.

Eiji

Eiji

No need to use apply as it’s much more useful if you have function name stored in variable.

`        response = @some_module_from_config.parse(data)`

Not only it would not give you any benefit, but it would also be wrongly used.

In Handler module you have to define callbacks and / or macrocallbacks. You use @behaviour attribute in CustomerA and CustomerB modules. In this tiny example it’s not important at all, but in general you use this to make sure that all callbacks are implemented as Elixir gives compilation warning otherwise.

egze

egze

I also recommend watching the keynote from José https://www.youtube.com/watch?v=agkXUp0hCW8
He goes into the topic what to use when.

dimitarvp

dimitarvp

How does your code branch on input data and decides which module to use?

I just recently had to do this and since I did not have a lot of modules and not too many input shapes I just made a router / dispatcher module that basically pattern-matches on the input and calls different module + function combo i.e.

module = Commands.detect_by_args(input)
module.perform(args)

Where Commands.detect_by_args/1 simply uses several pattern-matching function heads.

Eiji

Eiji

Behaviours are well documented, so it would be easiest to check it out. For example here is a quote I was referring to:

If a module adopting a given behaviour doesn’t implement one of the callbacks required by that behaviour, a compile-time warning will be generated.

Source: Behaviours | Typespec reference @ Elixir’s documentation

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
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
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
gonzofish
I’m currently trying to understand how to join three tables using Ecto. All the examples I’ve seen use 2, so maybe I’m just missing somet...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New

Other popular topics Top

quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
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
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement