pnezis
Elixir mono-repo best practices
We are currently in the process of restructuring our codebase. Till now we follow the monolith way (well structured though) but we have started facing scaling issues (especially on CI pipelines). Umbrella is not an option since we have multiple applications and we wish to split the configuration as well.
The main goals of this restructuring are:
- Speed up CI pipelines - by splitting the codebase into multiple packages we can build, lint test only the affected parts of the codebase
- Code organization and clear separation of concerns
- Better development experience - a developer working on the API does not need to compile dozens of
Broadwaypipelines orLiveViewapplications
A Ponzo like project with path dependencies seems as the best option:
- Maximize code resue
- Consistent tooling, code guidelines, CI pipelines
- No internal dependencies nightmare
On the other hand there are some caveats, the most important of which is:
- Each project will have each own
lockfile,depsand_buildpaths making CI configuration more complex (e.g. deps caching) and introducing incompatibilities between external dependencies
Since custom paths for lockfile, dependencies and build paths are supported for umbrella projects I was thinking the possibility of solving this issue by having a shared artifacts folder for all packages/applications ( something like rust workspaces with a common lockfile and output directory for all packages). Imagine a folder structure like the following:
project
├── .artifacts
│ ├── _build
│ └── deps
├── applications
│ ├── admin_ui
│ │ └── mix.exs
│ ├── api
│ │ └── mix.exs
│ └── data_pipelines
│ └── mix.exs
├── mix.lock
└── packages
├── package_a
│ └── mix.exs
├── package_b
│ └── mix.exs
└── package_c
└── mix.exs
Where each mix.exs will have deps_path, lockfile and build_path properly defined:
def project do
...
deps_path: deps_path(),
lockfile: lockfile_path(),
build_path: build_path()
end
...
I have tested this on a sample project and it seems to work fine. The documentation of build_path though suggests avoiding overriding this variable:
This option is intended only for child apps within a larger umbrella application so that each child
app can use the common _build directory of the parent umbrella. In a non-umbrella context,
configuring this has undesirable side-effects (such as skipping some compiler checks) and should be avoided.
What are these side effects? Does anybody has experience with elixir mono-repos? Any advice / alarms on the aforementioned structure?
Thanks! ![]()
Marked As Solved
mindreframer
I think this is the lib - Workspace - A set of tools for working with Elixir monorepos
Also Liked
pnezis
Poncho is the closest to what I want to achieve, my only concern with this is the different deps and build paths per project.
What I want to achieve is something like the cargo workspaces in rust
Cargo offers a feature called workspaces that can help manage multiple related packages that are developed in tandem.
A workspace is a set of packages that share the same Cargo.lock and output directory.
pnezis
Plain mix projects with path dependencies under a single git repo.
BradS2S
Agreed that diverging config by app under a umbrella project is an anti-pattern.
Functional Web Development with Elixir, OTP, and Phoenix starts by building the business logic as a separate application, without Phoenix. Might be worth thinking about breaking out your app as separate applications that are added as dependencies to the main app.
lafka
ping me once you publish something. Are you manually adding the paths between the different monorepo applications or do you have some way of discovering apps? Does your monorepo manage non-beam code as well?
pnezis
I want to keep the code in a single repository for multiple reasons:
- atomic commits between changes across packages
- avoid dependencies management nightmare (we have tried the poly-repo approach with a few internal packages and this corresponds to multiple commits in different repos for a single change)
- better visibility for the team
- common tooling, ci rules, and coding standards for all applications
- consistent e2e testing







