Interactors in Elixir following Clean Architecture guidelines

At the moment I am creating a plan to integrate Elixir at enterprise scale for several clients of the company I work for.

Part of this integration would mean the application should comply with the guidelines as outlined in Uncle Bob’s Clean Architecture. (A tldr version can be found here: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)

The part I am currently stuggling with is using Interactors in Elixir, without the use of any libraries.

How Interactors are usually implemented is by creating an Interactor interface (with all available functions/methods). The business-logic core expects an object where this Interactor is implemented, so the business-logic core can be fully agnostic of data sources and such.

An example of an interactor based design in Go can be found here: https://github.com/err0r500/go-realworld-clean/blob/master/uc/userGet.go

As far as I know there is no easy way to define an interface in Elixir, the way you’d usually do in languages like Go, Typescript, Java or even Scala. I was not able to find any good examples where this kind of architecture is implemented in Elixir.

Using typespecs/behaviours/protocols seems to make the code become quite messy if the Interactors grow in size, so I am looking for a way to implement this architecture while keeping the code clean and easy to maintain.

Thanks a lot in advance!

Behaviours is erlangs/elixirs way of having interfaces. Take for example a genserver. It expects a callback module, which implements the :genserver behaviour. When such a module is given to e.g. GenServer.start_link everything will be called correctly by the :genserver module without it needing to know about the specific callback module in advance. This is similar to how in oop you might expect an object, which implements a certain interface.

Thanks for the response!

How we currently implement the interactors is by building an object based on an interface in an outside layer, then pass that object as an argument to the function in the core. A screenshot of the dependency flow can be found here: https://imgur.com/a/MEYPGDN

I see how using behaviors can be used to create modules like you would implement an interface in OOP. What is still a bit blurry to me is how a module (that implements a behaviour) can be injected as an argument, and how the types can be specified (for type safety).

How could this be achieved in (vanilla) Elixir? Thanks!

mod = MyApp.BehaviourImplementingModule
MyApp.func(mod)

There’s no type safety on the beam. A module as typespec is module(), which is a alias for atom(). It’s basically working by ducktyping. If the correct callbacks are defined on the module of the supplied name and they accept/return the correct values everything is fine. If not it’ll fail at some time.

Thanks for your prompt reply.

Seems pretty clear for the most part.

The part still confusing me is how the module methods and their types could be checked at compile time.

For example, how could the MyApp.func method be typed?

What would the MyApp.func typespec look like to have compile-time checks on the mod (meaning MyApp.BehaviourImplementingModule which implemented MyApp.SomeInteractorBehaviour) passed to MyApp.func, to check if all MyApp.func's calls to mod would be “valid”?

Thanks a lot in advance!

You can’t. I just answered this on slack a few hours ago. I’ll just paste this here:

  • there’s only module() as built-in type or
defmodule MyBehaviour do
@type t :: module()
end
  • and MyBehaviour.t to at least be a bit more concrete for humans

  • also it’s not really a good fit for dialyzer, which will only error if it can be certain about a failure. Modules can be loaded at runtime, so there’s no way for dialyzer to check callbacks in advance

1 Like

Thanks for your reply. Trying to use Elixir this way is probably using the language in a way it’s not built for.

As the goal is to achieve a type-safe layered architecture (based on Uncle Bob’s Clean Architecture philosophy) and there are probably multiple ways to achieve this.

The thought of defining structs and passing these as arguments passed through my mind, but it seems like there is no way to define methods and method types in structs.

Do you have any ideas on how to achieve this kind of architecture in Elixir without any libraries?

I should also mention I have mostly been using Typescript for the past few years for back-end projects, and I’ve just recently started working with Elixir, so my knowledge about the language and the underlying philosophy is not very extensive.

Thanks!

You seems to be stuck at “type safety” which just doesn’t exist on the beam. There is no type safety. But GenServers work solely on the idea of a callback module being passed in and it’s basically the backbone behind most of everything running on the beam and it works well even without the type safety. You can have tests ensuring that a certain module conforms to a certain behaviour. You can use whitelists of some kind to allow just tested modules as inputs. If you really want to prevent someone passing in a wrong module there are ways, but not via types. I’d rather suggest making your app handle failure properly and weed out those bugs when they actually happen.

2 Likes

Thanks for the reply. I was under the impression Elixir did have compile-time checks, but that was probably due to me being an Elixir-rookie.

Do you have any further tips on achieving a layered architecture within Elixir, or enterprise software design with Elixir in general, as it seems like the amount of resources on this topic seems quite limited.

Again, thanks a lot for your help so far!

There’s dialyzer, which can do type checks, but it works considerably different to e.g. typescript. Bu even typescript would have problems with checking if a certain class implements an interface if the class could be created at runtime out of nowhere.

1 Like