Advice on structuring Elixir app

I am in the process of expanding my Elixir application and wanted to sanity check some of the patterns which have emerged to see what others think.

I am attempting to layer the structure for composition and clarity, but have fallen into the habit of extracting things into a kind of command pattern.

For example, instead of:

defmodule App.Users do
  def accept_invitation(invitation, user) do
  end
end

I would have:

defmodule App.Users.AcceptInvitation do
  def call(invitation, user) do
  end
end

My folder structure would be:

users/
  accept_invitation.ex

This has allowed me to build more complicated commands into separate modules/namespaces with just the relevant functions for that command instead of having lots of functions in the same module with mixed responsibilities.

I’m on a fence a little, but I can’t see any other way of keeping this clean as my application grows.

I have thought of a potential hybrid approach using defdelegate:

defmodule App.Users do
  defdelegate accept_invitation(invitation, user),
    to: App.Users.AcceptInvitation, as: :call

  def simple_function(user) do
  end
end

Which would allow App.Users.accept_invitation(user), and allow me to bundle with small, related functions which don’t necessarily need their own module. I like this because it keeps the API simpler from the outside.

Or what about:

defmodule App.Users.Invitation do
  def accept(invitation, user) do
  end
end

However, I don’t particularly like this as I have quite a few Ecto models and would rather not dump too many functions in them and keep them responsible for schema and validation.

Just trying to figure some patterns which will serve me well going forward. The command modules and single call functions are working well at the moment (especially having the file structure reflect the commands).

Thoughts?

4 Likes

I’ve seen the command pattern (with exactly the call method) in a large-ish Rails app. The intention was that the commands would make the controllers thin and allow for composition. While the first goal was achieved, it turned out that composition didn’t work that well really, mostly because of the fact that a single command would update the DB in a transaction and then perform some side effects (update an external system in a background job, etc.). So when composing such commands you either accept that some things are not transactional, you fiddle with passing the transaction up and down or you have to split pure and impure parts of the commands into two. Not saying that it’s impossible, but I didn’t like what I saw, so I’d be hesitant to adopt this in any new project.

The approach with commands looks like you’re creating a context (in the sense used by Phoenix) per function, which is the opposite of bundling everything together. I think there is some middle ground to be found but I think it might be difficult without having your domain-specific knowledge.