ojinari

ojinari

A universal way to detect environment in Phoenix?

When deploying a release via “mix compile / release”, Mix may not exist on a server, right? Therefore, if Mix.env() == 'abc' won’t work.

What is it then a way to detect the current environment? A one which would work everywhere, whether it be in dev or production or any other custom environment that I may create. And a one which won’t require copy-paste, and which will update itself automatically. Namely, merely using an environmental variable won’t work because what if I launched a project in prod. enviroment but locally? And then in dev. env again locally too. Or in dev. environment on a server? An env. variable would hold the wrong value half of the time - unreliable solution.

Also note that on a server I may use systemd or rc.d service, therefore simply using .env file although would work locally in dev env, wouldn’t on a server because it’d require copy-pasting the env. data into systemd or rc.d services and I want to avoid copy-paste.

Marked As Solved

soup

soup

Why do you have to have the if in code and not just inject the correct delegate when the service boots though?

Anyway, I believe Jose is saying set whatever you want to check in the appropriate config file, you can then get that with Application.get_env or you can use System.get_env if you want to get an regular system environment variables. You could probably do config :my_app, :execution_env, Mix.env() in config/config.exs I guess then Application.get_env(:my_app, :execution_env).

I think Jose is also implying that instead of checking against the env en todo, it’s better design to check against specific configurations, which is more readable and maintainable:

if Application.get_env(:my_app, :do_use_service) ...
if Applcation.get_env(:my_app,  :logging_is_enabled) ...

vs

if Application.get_env(:my_app, :mode) ==  :dev then do_use_service()

See h Application.get_env and h System.get_env in iex.

Also Liked

LostKobrakai

LostKobrakai

Values can affect behaviour though and that’s what people are trying to suggest here.

Say you have a UI where you show a secret key. In dev you want to show the whole secret, while in prod (and other envs) only the last few characters are supposed to be shown. This is different behaviour.

defmodule MyUI do
  defp format_secret(<<_start::binary-size(12), rest::binary-size(4)>> = secret) do
    case Mix.env() do
      :dev -> secret
      _ -> "…" <> rest
    end
  end
end

Instead of depending on the environment to select behaviour directly you can bundle up the behaviour (e.g. in functions or modules) and let the environment select the behaviour via configuration.

Step 1: Bundle up behaviour

defmodule SecretFormatter do
  @callback format(binary) :: binary
end

defmodule SecretFormatter.SecretFull do
  @behaviour SecretFormatter

  @impl true
  def format(secret), do: secret
end

defmodule SecretFormatter.SecretTail do
  @behaviour SecretFormatter

  @impl true
  def format(<<_start::binary-size(12), rest::binary-size(4)>>), do: "…" <> rest
end

defmodule MyUI do
  defp format_secret(secret) do
    case Mix.env() do
      :dev -> SecretFormatter.SecretFull.format(secret)
      _ -> SecretFormatter.SecretTail.format(secret)
    end
  end
end

Step 2: Use config to select the behaviour

# config.exs
config :my_app, MyUI, secret_formatter: SecretFormatter.SecretTail

# dev.exs
config :my_app, MyUI, secret_formatter: SecretFormatter.SecretFull
defmodule MyUI do
  defp format_secret(<<_start::binary-size(12), rest::binary-size(4)>> = secret) do
    formatter = Application.fetch_env!(:my_app, __MODULE__) |> Keyword.fetch!(:secret_formatter)
    formatter.format(secret)
end

This is quite a bit longer as you can see, but this is also the most elaborate setup with gives you a lot of compiler help, the ability to mock things using Mox in tests and such. You could also just name the two different ways to format things and configure the name for each.

# config.exs
config :my_app, MyUI, secret_format: :tail

# dev.exs
config :my_app, MyUI, secret_format: :full
defmodule MyUI do
  defp format_secret(secret) do
    case Application.fetch_env!(:my_app, __MODULE__) |> Keyword.fetch!(:secret_format) do
      :full -> secret
      :tail -> "…" <> rest
    end
  end
end

The important bit is that you put the different code paths (currently per env) into something independently callable and make the decision which of those to call a configuration concern instead of compile time or runtime decisions in the codebase itself.

The second example might also show that Mix.env is also just a value you use to make decisions. It’s just way less flexible and less visible than using configuration. Configuration allows you to map env => behaviour in one common place instead of scattered all over the codebase.

soup

soup

Because this may infer a lot implications that are hard to understand for a second person (or your future self).

# checked in
#   - my_mod.logger to decide on which logging endpoint to use
#   - my_mod.xyx.fn() to infer abc or ars.
config :my_app, :mode, :dev

And then one day you set :mode = :prod but the check never accounted for it or forgets to check and you have a very hard to find bug in some obscure location.

Or you want to have two staging envs and now you’re updating all your if/case to check env == :staging or env == :staging_site_b.

VS

config :my_app, :remote_logging_url, "https://dev.local"
config :my_app, :xyz_use, :abc

Which is both explicit in what you are configuring and allows for finer configuration and should actually remove the need for checks in your code.

case :mode do
  :dev ->  Logger.set_receiver("http://dev.local")
  :staging ->  Logger.set_receiver("http://staging_1.internal-1.com")
  :prod -> Logger.set_receiver("http://www.remote.com")
end

becomes

Logger.set_receiver(Application.get_env(:my_app, :remote_logging_url)
josevalim

josevalim

Creator of Elixir

The thing is that hardcoding Mix.env() checks in your application is confusing and error prone. Instead, try using the application configuration and setting the proper values in your dev/test/prod. This way an operator of the system can see all behaviour that is different between dev/test/prod without having to grep for Mix.env() checks in the codebase.

Where Next?

Popular in Discussions Top

Jayshua
I recently came across the javascript library htmx. It reminded me a lot of liveview so I thought the community here might be interested....
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
jeramyRR
This is an interesting article to read. Elixir’s performance, like usual, is excellent. However, it seems like the high CPU usage is co...
New
cvkmohan
The upcoming Phoenix 1.6 release looks very interesting. Became a habit to watch the commits - and - what they are bringing in. phx.gen...
New
laiboonh
Hi all, I am trying to convince my team to use liveview over the current react. What are some of the points where one should consider us...
New
fireproofsocks
I’ve been working on an Elixir project that has required a lot of scripting. I usually reach for Elixir because I like it more (and in th...
New
hazardfn
I suppose this question is effectively hackney vs. ibrowse but we are at a point in our project where we have to make a choice between th...
New
restack_oslo
Hello, Please pardon me for any faux paux. I am 46 and this is my first time on a forum of any kind. I wanted to to get answers from tho...
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
Qqwy
Looking at the stacks that existing large companies have used, WhatsApp internally uses Mnesia to store the messages, while Discord uses ...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement