How to organize behaviour files in a project?

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?

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”.

1 Like

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.

1 Like

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.

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:

1 Like

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.

2 Likes