How do we organize our project

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 cloud_app declaring 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 cloud_models_api project.

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
1 Like

Seems like you wanna do synchronisation and communication via DB. In that case I have only one answer for you - don’t.

lol, good guess but no. We just already have another application taking care of syncing (just not via the DB).