Fl4m3Ph03n1x

Fl4m3Ph03n1x

Background

We have a project that has behaviours. Normally I try to follow the community guidelines regarding files and folder organizations, but today one of my colleagues made some good points about why this may not be ideal.

Organizing behaviours

Let’s say I have a behaviour called dispatcher, which defines an interface that other modules must implement.

Usually, I place the behaviour at lib level and then I place the behaviour’s implementations in a folder with the behaviour’s name:

lib
  |_dispatcher
  |         |_my_dispacther.ex
  |_dispatcher.ex

Issues

Upon checking this my colleague made a remark: “when I look at your organization, I think that dispatcher is actually a module with functionality, i.e., I think it is the real dispatcher.

I can understand him. The file’s name does not state it is a behaviour, nor a contract nor anything alike. I argued this is the way the community does it (which is a poor argument, I do things because others do it) and I couldn’t really suggest anything better (still have a lot of PragDave’s videos to watch on project organization… )

Brainstorm

We decided to come up with some ideas, but to me they all fell short:

  1. Rename dispatcher to i_dispacther. This falls short to me, because behaviours are not interfaces, nor contracts. They are actually a superset of the strategy design pattern.
  2. Rename to b_dispatcher. To me this sounds useless. It’s like when someone asks your name and you answer “I’m JohnPerson”, or “I’m TimCat”. The biggest issue I have with this, comes from the arguments given in this talk though: https://youtu.be/ZsHMHukIlJY?t=1422
  3. Move everything into the dispatcher folder. However, doing this means that the convention of App.FodlerName.Modulename would be meaningless. You would aggravate the issue of lego naming by transforming it into App.FodlerNameModulename.

Questions

How do you organize your project structure so that behaviour files are clear?

Showing Posts 1 to 5

NobbZ

NobbZ

Usually I do have a lib/protocols/ which contains actual protocol definitions. The organisation there follows the usual pattern but ommitting the first layer of the “namespace”. So if my protocol were Foo.Bar I’d put it into lib/protocols/bar.ex, also I’d put the defimpl for all “native” and “foreign” data types there.

While I’d put the defimpls of my own structs in the files that define the struct:

defmodule Foo.Struct do
  defstruct field: 0

  defimpl Foo.Bar do
    # actual implementation
  end
end

edit

After posting my reply, I realize your final question:

I usually do not special case behaviours and have them where they belong to the regular “rules”.

peerreynders

peerreynders

1.2 Behaviors:

Behaviours are formalizations of these common patterns. The idea is to divide the code for a process in a generic part (a behaviour module) and a specific part (a callback module).

The generic part should be kept where shared/reusable code is kept. The specific part should be with the other code that implements a specific capability.

However if a Behaviour only exists to make testing more flexible rather than capturing some generic aspect then it simply belongs with the capability it supports.

Note that I’m assuming an organizational principle where code is organized by capability rather than collecting “like things” in folders.

And while Erlang has gen_server, gen_statem, gen_event there are behaviour modules that don’t have the gen_ prefix (e.g. supervisor). And just because you implement a gen_server doesn’t mean that the call module will necessarily have a _server suffix.

I think it is the real dispatcher.

In terms of that particular capability that is likely correct but that doesn’t take away from the fact that dispatcher.ex is intended to be the foundation for a whole family of dispatcher implementations.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

In this specific case, dispatcher.ex only has callback specifications. It would be like a good old Java/C# interface. No logic, just a simple contract.

I take it from your post that in this scenario, you would create a folder called “dispatcher” and put all the dispatchers inside it, because they all have the same capabilities.

lib
  |_dispatcher
            |_my_dispacther.ex
            |_dispatcher.ex

But what about the community norms that defend that for each folder there must be an .ex file with it’s name at the same level?

lib
  |_dispatcher
  |         |_my_dispacther.ex
  |_dispatcher.ex

I see you agree with PragDave’s point, but it is rather confusing to me to accept that the Elixir community has a not so good project structure by default.

peerreynders

peerreynders

I take it from your post that in this scenario, you would create a folder called “dispatcher” and put all the dispatchers inside it, because they all have the same capabilities.

No. If you look at the post that I linked to there was an inventory capability with a number of “services” under it. If each service had its own dispatcher callback module I would put it inside the corresponding service folder. The behaviour module would end up on a higher level:

  • under inventory if the dispatcher is inventory specific.
  • above inventory if it is more generic

(Using module names as namespaces)

But what about the community norms that defend that for each folder there must be an .ex file with it’s name at the same level?

By all means start out with When In Rome. But find out what the exact justification for the convention is - you may find that at some level some assumption may conflict with your particular situation. Then you have to make a decision whether or not it is worth being different (including documenting exactly why you think you’re special - a plain “I don’t like it” isn’t enough).

For Example:

trarbr

trarbr

I’ve used behaviours in a couple different ways depending on the context.

Single use contracts

Some times I use behaviours as contracts - they simply list callback functions, but contain no generic functionality. I use this mostly in the boundaries of my code - boundaries between my application and libraries, and boundaries between pure functions and impure/side-effecty functions. Which enable me to test these things with Mox.

For example, I have an application using Redix, a Redis library. It contains code like this:

# lib/my_app/redis.ex
defmodule MyApp.Redis do

  @behaviour MyApp.Redis.Contract
  @redis_mod Application.get_env(:my_app, :redis_mod)

  def start_link(url), do: @redis_mod.start_link(url)
  def command(conn, command), do: @redis_mod.command(conn, command)
end

# lib/my_app/redis/contract.ex
defmodule MyApp.Redis.Contract do
  @callback start_link(binary()) :: GenServer.on_start()
  @callback command(GenServer.server(), list()) ::
    {:ok, Redix.Protocol.redis_value()} | {:error, atom | Redix.Error.t()}
end

These two files are both pretty skinny. Depending on mood, I might have put them both in one file, but I did put them in two files - and the behaviour file is namespaced below the module that uses it.

Multi-use contracts

In a Nerves application I’m working on, there are multiple places where I interact with the OS on the device. Everytime I do this, I need separate implementations for when the code is running on my dev machine and when it’s running on a target. So I have four modules:

  1. A MyApp.OSThing.Driver file with callbacks (like MyApp.Redis.Contract).
  2. A MyApp.OSThing.Target file which implements the driver behaviour for when the code is running on a target
  3. A MyApp.OSThing.Driver file with implementation that’s specific to my dev environment
  4. The MyApp.OSThing module which has all the business logic, and which has a module attribute with the name of the module that should be invoked (as in MyApp.Redis above).

Generic behaviours

And then there are the cases where it’s a “real” behaviour which implements generic functionality and invokes functions on a callback module to handle specifics. Here, I’d probably have a MyApp.GenConnection module, with a MyApp.Connection callback module using it, both placed in files on the same level.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews