8) ElixirConf US 2018 – Growing Applications and Taming Complexity – Aaron Renner

ElixirConf US 2018 – Growing Applications and Taming Complexity – Aaron Renner (@aaronrenner)

Growing an application is hard. We started our app with the best of intentions and promised ourselves we were going to “design it right". However as the app grew, complexity increased, dependencies piled up, and changes that were once small now felt like slogging through mud.

Driven by the need to keep our sanity, we decided to apply ideas from Phoenix’s contexts and Domain Driven Design with the hope that we could put boundaries around the complex parts of our system and keep the code from turning into a tangled rat’s nest. This talk covers many of the lessons we learned along the way, including:

  • How to start tackling complexity by teasing apart the app into multiple layers.
  • How to simplify testing and reduce mental burden by defining contracts between these layers.
  • How to verify contracts using tools like dialyzer, automated tests and mocks.
  • How to document these contracts and use them to guide the application design.

After applying these ideas to our codebase for almost a year, we’re eager to share our experiences and provide strategies you can use to tame complexity in your own applications.

Audience: Intermediate
Topics: #phoenix

All talks are available in the ElixirConf US 2018 Talks List or via the #elixirconf2018 tag


I am having mixed feelings about this, the examples in repository (https://github.com/aaronrenner/crowdfundr) even define some very common cases like user entering an email that is already used as an exception (Crowdfundr.EmailAlreadyRegisteredError) and then use that in {:error, exception} tuple.

I am just learning Elixir but I have went through some materials already and I must say this approach looks totally foreign to me. I would even say that it looks like a total oposite of why I turned to Elixir in the first place. The Behaviour/Impl/DefaultImpl thing reminds me too much of the “interface with single implementation” ceremony that used to be common in some OO languages.

What do you think? I would love to hear what others have to say.

1 Like

I‘ve not had a look at the repo yet, but it‘s probably created by driving the point of the talk all the way to make a point.

On the other hand having clear interfaces is something, which is by no means inherently OO. Interfaces are something your code has even if they‘re not written out somewhere explicitly. At the least you should have it as soon as there are multiple implementations, which can be switched out (and as @josevalim argued when creating Mox, this stands also if a second implementation is just used for testing). This was one point in the talk, that it makes testing easier. But I‘m with you that it‘s quite a bit of ceremony if there‘s only one implementation. It just makes is super clear, which functions are meant to be public interface vs. what is implementation detail. In bigger projects, this might otherwise not be as clear.

Naming errors is imo a good thing like anywhere else in programming, especially for the common cases, which are the ones one has to deal with all the time. It could‘ve been an atom instead of an exception struct, but the latter has the ability to later be enhanced with metadata, while with a plain atom to start with the interface would probably need to change in a backwards incompatible way.


I really liked the talk! It’s basically advocating the use of the Hexagonal Architecture as a way of tackling growing complexity. Just as any pattern, Hexagonal Architecture has pros and cons. The cons are: more abstractions and indirection. It’s up to the developer to decide if the pros outweigh the cons.

Functional programming does not solve complexity on it’s own. I’m working on a Rails app with a god-class - I can clearly see how one could translate this line-by-line into the Elixir land and cause the same pains. Really, there’s no such thing as an anti-pattern: if you’re hacking a throwaway prototype, then cramming business rules into controllers is fine in my books. I guess most web apps would be fine with the “defaults”, i.e. Ecto leaking into the controllers. But that’s not always the case, especially with growing complexity.


The behaviour/impl module and separate implementation approach is used by the new Elixir Telemetry library.


I know it’s over a year later, but I just posted an in-depth article covering the layering and swapping mechanisms I gave an overview of in this talk.


The behaviour/impl module and separate implementation approach is used by the new Elixir Telemetry library .

Unfortunately this no longer applies as the Telemetry library has been rewritten from Elixir to Erlang.

1 Like