Best practices: Umbrella app with Multiple Repos

I’m hoping for some input from the wiser folks, but I’m wondering if there are some best practices when setting up an umbrella app with multiple repos. Freshly recovered from the sunburn of a legacy monolithic app, we have structured an umbrella app that seems to provide a good balance between the various concerns while maintaining an easily tested whole. It’s possible that each app under the umbrella uses its own database, but in practice, most of them will tap into the same PostGres instance.

However, one potential problem I’ve noticed came when we deployed the app: because many of the individual apps have their own repository, that means that the app requires more connections. We’re using Gigalixir and the basic first tier database supports 25 connections, but between 6 or 10 apps, that gets gobbled up pretty quickly, so for each app I had to whittle down the connections to 2 or 4 connections per app before the error messages went away.

My question for the group: is this a bad way to structure an umbrella app? I love the flexibility of being able to isolate services potentially into their own databases, but is it worth the extra headache of requiring more connections?

Thoughts welcome! Thanks!

3 Likes

This seems like a bad idea for umbrellas. Umbrellas are one app. They are. They are not one repo with multiple different apps. They are one app with multiple concerns. They are one app.

Do you want to feel deploy them as one or make seperate deployments? If that’s so, I think what you are looking at is poncho apps. Since they are their own report, I think you should move away from umbrellas and move into poncho.

As per the DB, this means memory usage will increase for each new app. If that’s ok, go for it. The amount of connections will be dependent on what each app requires. Normally I just worry about that later when load increases, but only if this current config can sustain the current load.

2 Likes

Hmmm… I’ve never heard of poncho apps and have only found 1 rather cursory post about them.

I agree that what you describe sounds more like poncho projects, which was pioneered by the Nerves team and also something Dave Thomas has used with his component-focused approach. The original writeup I’m familiar with is here https://embedded-elixir.com/post/2017-05-19-poncho-projects/

In my understanding I make a distinction between apps and projects. Projects are how code at rest (on the filesystem) are organized. Apps (in the OTP sense) are a runtime concern – basically a branch of a supervision tree. You can have a 1-to-1 mapping of project to app, or a 1-to-many. Umbrellas are projects with a 1-to-many mapping to apps. Things get more flexible when you also start thinking about releases. Releases are deployable units of apps. You can have multiple releases out of one umbrella, each using some subset of the contained apps. Distillery supports multiple release targets specifically for this purpose.

I like umbrellas. They fit well with the type of app I’m familiar with. I make one mitigating change from default umbrella setup to run each app’s tests in a separate BEAM to suss out any improper coupling early. Umbrellas may not fit everyone, but I’ve also heard a lot of misinformation.

4 Likes

This isn’t true at all. Umbrella contain separate applications, and while sometimes these depends on each other it is not hard requirement and you can easily deploy them independently from each other.

Ponchos are just different form of keeping projects in the same repository. Nothing prevents you from using Poncho like Umbrella structure and vice versa (ok, using :in_umbrella will be harder, but that isn’t much of the problem).


You do not need Umbrella for that. Nothing prevents you from having multiple repos in the same application.

We cannot answer you to that question, as this highly depends. In your case you are very limited by the number of the connections, so you have your answer. In bigger systems? Who knows, sometimes it can be solution and sometimes it will not be.

2 Likes

Interesting thoughts – this has given me a couple ideas of things to try, so thank you!

Unfortunately, the idea of the poncho app is currently too vague to be useful to me. From that one short post, I have no point of reference having never built a nerves app or built firmware etc. If someone could write up something more thorough demonstrating where a poncho might work vs. where an umbrella would not, that would be something…

Yes, you can. I used to think this for a while as well. But umbrellas are meant to be their own application. Deploying individual apps with umbrellas is possible. However, they are one app.

I would like you to set up an umbrella app, install Phoenix 1.4, then install another app with Phoenix 1.3. You’ll get dependency conflicts. For more complex umbrella applications, this has been a major problem for moving certain apps forward since you’re forced to do a project-wide update instead of being able to update each app individually.

The idea of a poncho app is really simple. It’s just like an umbrella app but instead of a single OTP app ruling everything, you have mulitple mix.lock files instead of a single mix.lock that can cause issues.

I think the big thing is with dependencies. At my current company, we’re using umbrellas to have very separate apps and deploying them individually. When we went to install Phoenix 1.4 into another app, we were blocked because we had another app that was stuck on Phoenix 1.3. I soon learned that this is what Poncho apps were meant for, not Umbrellas as highlighted by the Slack channel.

I’m not sure what it takes to convert an umbrella to a poncho, but instead of doing in_umbrella: true, you instead just specify a path.

You keep emphasizing that, but I cannot agree. Yes, they must have a consistent set of dependencies, and they have a mix.exs file, but that is not sufficient to make them an OTP application. Their mix file is lacking an application configuration. They have no Application behaviour module and no supervision tree. Umbrellas are best thought of as a “project” of shared configuration and dependencies.

1 Like

That’s what I thought too but it was very much explained in the slack that they are just a single app and people should be using ponchos.

This is where you lost me. If all these “Repos” are using the same Postgres instance, I’m skeptical there should be multiple Repos at all. Your apps can depend on each other, so you could have a shared Repo app that is used by all the different components, and yet each of those can have their own schemas, which may overlap or not depending on your architecture.

4 Likes

I’m skeptical there should be multiple Repos at all.

Yes, that’s exactly the conclusion I’ve come to. When I started building the apps individually, it made sense for them to have their own Repo and database because it made it easy to dev and test in isolation and each app didn’t need to know about the umbrella at all.

In practice, however, those individual repos are inefficient when they all are pointed at the same database when they get run under the umbrella. The more efficient solution, which you outlined, is to have a repo shared by all the apps (most of the apps?). If one app needs to use a separate repo (e.g. something other than PostGres, or maybe just to point to a separate instance for reasons of performance/security/???), then it would be relatively simple to replace the shared App.Repo with a different adapter.

1 Like

Yet ironically, just within the past hour the phoenix channel on slack has given accurate information on umbrellas.

I wonder if the advice you got was related to needing 2 different version of phoenix and somehow got overgeneralized in your head. Or it’s possible whoever was around when you asked the question answered with more confidence than they should

1 Like

Just to make this clear, there’s not a “single OTP app” in an umbrella. The top abstractions on the beam is an otp app and umbrella and poncho are both ways to compose multiple otp applications together. The difference is that an umbrella is a single mix project, which combines multiple nested “mix projects” into one – starting any of them by default, while poncho’s approach is developing each application in it’s own mix project and using simple {:dep, path: "…"} dependencies between each other, where there’s one “root” application, which depends on all the sub applications.

This sounds like you’re missing some abstraction here. For me MyApp.Repo is mostly a runtime construct handling db connections to a certain database (pooling and all that stuff). And it sounds like you want to reduce coupling to the app holding that module. Normally you’d do that by inversion of control. Create a behaviour in each app, which handles the concern of “accessing a db” and use configuration to let it use MyApp.Repo in prod and some other implementation in development/testing. If you want you could even reuse Ecto.Repo as the needed behaviour, as ecto already has a behaviour in front of the actual implementation within the library.

6 Likes

Yes, some abstraction here would be helpful…

Let’s say my umbrella has some apps that require a PostGres Repo and some apps require a MySQL Repo. Instead of the application.ex having something like supervisor(AppOne.Repo, []), I could instead create a couple dedicated apps under the umbrella for PostGres.Repo and MySQL.Repo and then in app_one I could define a config something like:

config :app_one, repo: PostGres.Repo

And then I could reference it in my contexts something like this:

defmodule AppOne.Contexts.SomethingContext do
  
  import Ecto.Query, warn: false

  @repo Application.get_env(:app_one, :repo)
  
  alias AppOne.Schemas.Something


  def get(id), do: @repo.get(id)

end

I think that will work and provide useful abstraction. Is that what you were thinking?