Hey yall, I have a project organization question:
We are building two internal elixir apps (i’ll call them “local_app” and “cloud_app”), which share a ton of code so we decided to go with a monorepo strat.
Here are all the elixir projects under the repo. (Note: only the
_app projects are elixir applications. the other projects are simply changesets and functions.)
home ├──local_app ├──cloud_app ├──shared_models ├──local_models_api └──cloud_models_api
Basically, what happens is that the we needed both the local_app and cloud_app to know what a shared_model is, so we abstracted away those schemas and changesets to the “shared_models” project, which both _app’s declare as a dependency using the
path: ../shared_models option.
We need both apps to know how to read those models, however only one app should have right access to a specific model. For example, we might have a
SharedModel.Note schema, which both apps will know how to read, and the API to get the notes might live there too (
SharedModels.Notes.list_notes/0). However, we only want the local_app to have write privileges to those notes—this is so important that we don’t even want the write functions available to cloud_app at compile time—so we made a
local_models_api project that exposes functions like
LocalModelsAPI.Notes.create_note/1 and only the local_app uses that as a dependency.
(The reason we needed that code to live in a separate
local_models_api project and not just live in the
local_app project is because we needed to write test helpers for the
cloud_app test suite that writes a handful of those models in order to test some other behaviors, so we have
local_models_api and a test only dependency.)
Vis-versa, we only have some models that we want write access to via the cloud_app, so we created the
This leads to messy alias APIs in our application code, for example, its really common for a server_app controller to have
use LocalWeb, :controller alias LocalModelsAPI.Notes alias SharedModels.Notes.Note
and we use the
defdelegate pattern at the bottom of the LocalModelsAPI.Notes module to bring in all the SharedModels.Notes functions into the LocalModelsAPI.Notes namespace (which leads to compile time warnings that functions don’t exist, but we ignore those).
It also leads us to have a messy test pattern. For example, we want to test our LocalModelsAPI logic in our local_models_api project, but because it doesn’t have its own stand alone application, it cannot run a DB. So instead, we added the
"../local_models_api/test" to the
test_paths list in our LocalApp mix file. (Which then leads to doubling our tests for the SharedModels project, but that’s fine.)
Is there a simpler way?
To recap, the problems we need to solve:
- Have a shared library of models
- Have some APIs of those models only available to specific projects
- Have all those APIs available to all the projects test helpers