Providing start options to application dependency

I wrote an OTP app that I want to reuse within other applications as a dependency. This app has a DynamicSupervisor to start/stop processes. DynamicSupervisors can be started with a :max_children option to limit concurrency, I’d like to expose that when the app is used as a dependency.

Currently, in the Application.start/2 callback, I make use of the 2nd argument to determine whether or not the number of the DynamicSupervisor’s children should be limited (i.e. forwarding the option, if present, to the DynamicSupervisor). However, it appears that’s maybe not the way to do it, as I’m having trouble figuring out how to provide the option to the application on startup.

One possibility would be to have {:my_app, ..., runtime: false} as the dependency specification (so it doesn’t get started up with empty options) and then manually start the application somewhere with MyApp.Application.start(:normal, max_children: 5) where MyApp.Application is the module implementing the Application behaviour (via use Application). But then how could I have this in the supervision tree?

Or should I define a child_spec function to handle this? Something like (in MyApp):

def child_spec(arg) do
  %{
    start: {__MODULE__, :start, [normal, arg]}
  }
end

This would allow me to have e.g. {MyApp, max_children: 5} as a child of the top supervisor, but it won’t be linked.

Should I be going about this in an entirely different way?

1 Like

Any ideas on how this can be approached? Anybody have any example of an OTP app that uses startup params to configure behavior? As far as I know, the typical approach thus far has been using application env to specify the configurable values, but it’s my understanding that the community is moving away from this (per https://hexdocs.pm/elixir/Application.html#get_env/3 and https://hexdocs.pm/elixir/library-guidelines.html#avoid-application-configuration). Unfortunately, the the anti-patterns section doesn’t cover startup params.

There is no way to change the application startup arguments other than changing generated .app file. Instead you should use Application.get_env/2. You have misunderstood the guidelines, un case of the applications the environment is the best option, the idea is to avoid applications when possible (but that still heavily depends on what you are writing).

An argument can be made that you can just start the app with sensible defaults, not using Application.get_env/2 anywhere, and write it in such a way that the configuration can be changed at runtime. That’s what I am opting to do lately in my work – although I’ll admit for part of the cases it’s an overkill and having a boot-time config that’s not easy (or impossible) to change is sometimes a feature.

Yes, just use :env field in application definition and then you can change that via Application.put_env/3.

I do indeed typically strive to design applications in such a way that values can be provided/changed at runtime, but in this case I don’t see a straightforward way to do so: on startup, the app starts a DynamicSupervisor within its supervision tree, and the :max_children value can only be provided to the DynamicSupervisor within the init callback.

So it looks like, in this particular case, I don’t have any other options besides Application.get_env/2 as explained by @hauleth (thanks!).

The app env is usually frowned upon for making config global state. For applications though can can only ever be one per node anyways so it‘s as global as any application env, so there‘s no mismatch for wanting to run more „instances“ as you can configure.