Migrating off of an umbrella app structure

The reason we’ve started to seriously consider migrating off umbrella app, is a particular limitation we’ve noticed when using mix xref graph tool. It was a bit of a journey, so let me explain.

Recently several developers in the company reported that our application code compilation times have increased to the point they’ve become quite uncomfortable for day-to-day work.

We’ve started closely looking into what’s going on, and were able to identify and fix many low-hanging fruits using mix xref graph tool, which is a static analysis tool that reveals module dependencies. Someone suggested a gist with a tutorial for using mix xref on a real project. That gist was instrumental in helping me build a mental model around what kind of work needs to be done to reduce app re-compilation times.

After reaping the low-hanging fruit came a moment where we hit a limitation: mix xref graph will not show cross-application compile dependencies. It’s best illustrated based on example.

One of our apps is called api another one is called domain. mix xref graph --label compile-connected successfully shows dependencies between modules within a single app, but not between api and domain. While obviously there are many instances, where for example a controller from api app depends on a module from domain app.

Removing umbrella app helped us solve this, so running mix xref graph --label compile-connected now reveals the “full picture” and helped us see more opportunities for re-compilation optimization.

However, after years of running an umbrella app we’ve accumulated more issues with it:

  • the way we handled sharing code between apps - by extracting common code into yet another app - proved problematic,
  • the need to have a dedicated files like test_helper.ex, factory.ex etc. in each individual app,
  • a somewhat non-obvious connection between root project mix.exs respective file in each individual app,
  • calls to Path.join("../../..") across tests when in need to access something shared between the apps,
  • overall speed of test suite (suites for each individual app needs to run sequentially one after another),
  • mix test test/app/path/to/file_test.exs were never actual real path to files, but rather “path pattern” to look for a test file within the individual app,
  • some packages don’t work well with umbrellas (example)
  • etc.

So, small DX stuff like that. Not to mention we’ve developed a habit to attribute sporadic elixir-lsp misbehavings to our app being umbrella.

In the end of the day I tend to think no one on the team really understood or valued the benefits of umbrella app structure well enough to make a case for keeping it. Our app is pretty straightforward, REST web server application, and most of us craved for less obstacles in a day-to-day work.

2 Likes