Why `otp_app: :my_app` when there is Application.get_application/1?

Hello. I recently discovered Application/get_application/1 and now wonder why specifying the otp app with use in libraries is so prevalent?

The only thing I can think of is that they need the otp app as compile time info, but how and why? Usually it’s simply used to fetch config with something like Application.get_env(@otp_app, __MODULE__, []) which is a runtime operation anyway.

So fetching config becomes like this instead:

Application.get_application(__MODULE__)
|> Application.gen_env(__MODULE__, [])

If for some reason you want to config your module outside of the app it was defined in, you can do something like:

(@use_opts[:otp_app] || Application.get_application(__MODULE__))
|> Application.gen_env(__MODULE__, [])

After removing otp_app: :my_app from all my libraries and switching to Application.get_application/1 to get config, everything seems fine… and it’s really nice having less things to configure (especially in a large umbrella app where modules get moved around to different apps frequently), but I’m wondering if there are any hidden dragons to this (and if not, why this pattern is so prevalent in libraries).

Thanks for the info!

1 Like

Application.get_application/1 is slow an error prone. What it really does is (simplified):

Application.loaded_applications()
|> Enum.map(&{&1, Application.spec(&1, :modules)})
|> Enum.find(fn {_, modules} -> module in modules end)
|> elem(1)

So if you are doing it often, then It can provide quite substantial penalty. Additionally it will (obviously) not work with modules that aren’t listed in :modules section of application description (for example dynamically loaded modules). Last, but not least, is that Application.get_application/1 will obviously not work during compilation, as you have already noticed.

1 Like

Interesting, thanks for the info.

I usually use it in repo pattern libraries in start_link.

def config do
  (@use_opts[:otp_app] || Application.get_application(__MODULE__))
  |> Application.get_env(__MODULE__, [])
end

def start_link(config \\ []) do
  {gen_opts, config} = Keyword.merge(config(), config)
  |> Keyword.split(@gen_opt_keys)

  GenServer.start_link(__MODULE__, config, gen_opts)
end

So it’s typically is only called when starting up a repo (or debugging the repo by calling config/0). Seems reasonable.

How about :application:get_application/0? Sometimes I want to know the application at run time. For example, if my library provides a GenServer to be started as part of the client’s supervision tree, and I want to get the client’s application name reliably. Is is slow and error prone too?

It will use similar approach to what I have described above for modules, for PIDs it will traverse supervision trees to find application tree it belongs to. That mean that if you start process outside your tree it can return unexpected results.

2 Likes

Thanks, so it is slow, and it make sense to do it once and store the result.