In Phoenix, if I "let it fail" I get 500 errors?

I like the “let it fail” mentality in Elixir and Erlang, but when I use that mentality in a Phoenix controller it returns HTTP responses with status code 500. It’s my understanding that a well behaved server shouldn’t be regularly sending 500 errors.

In order to avoid these 500 errors I found myself doing a lot of manual type checking and data validation. It’s really cumbersome. All I really want is to say “if anything fails in this controller, return a 400 error”.

I guess you’re missing out the point of “let it crash” and misunderstanding the http errors.

The “let it crash” philosophy makes your server stay up. Even if the code crashes it doesnt matter why it crashed, your application will still be running and other requests may be attended.

If your code crashed the correct response is a 500 error. Phoenix and Elixir cant find out why your code crashed and say “oh, here he wants a 400 error, now in this other crash he doesnt want anything so lets go with 500”.

3 Likes

The “let it crash” approach solves the problem of catching exceptions and trying to resolve server state. When you use this approach, your exception handling can get very complex quickly. If you let it crash, the supervisor will restart your process with the initial state given to the process when it first started. This allows the process to startup in a deterministic manner.

By chaining processes together (restart strategy), you can make sure process dependencies are correctly synchronized too.

@cevado is correct, this does not apply to the web model. If you crash in a web request, your going to get a 500 error.

2 Likes

The action_fallback feature in Phoenix 1.3 combined with with blocks reduces the boilerplate considerably.


action_fallback MyApp.Web.FallbackController

def create(conn, params) do
  with {:ok, cmd = %CreateUserCommand{}} <- build_create_user_command(params), # cast params to embedded ecto schema
       {:ok, user = %User{}} <- create_user(cmd),
       {:ok, job_id} <- send_welcome_email_async(user) do 
    render(conn, "show.json", user: user) 
  end
end

As long as the FallbackController has a clause that can match the {:error, %Changeset{}} or {:error, reason} tuple from one of the failing steps then you’ll get a 4xx range response.

4 Likes

I was reading the thread and I just realized that what I said seems very harsh.
Sorry, that wasn’t my intention at all. :disappointed_relieved:
I was late and wrote it really fast.

Just trying to contribute a little better to the topic. Maru has a great dsl and a great way to validate parameters, that can be used with phoenix without much work. I know that this is not the point of the topic, but may help validating parameters from a request.

One way to think of the “let it crash” mentality is to look at it like this:

  • The process that lives inside of the Erlang VM is allowed to crash. Sometimes there’s something unexpected that happens (maybe the API introduced rate-limits or we got invalid input) which we didn’t anticipate. We can recover from crashes like this by using self-healing capabilities available in OTP.

  • The Erlang VM process running on the operating system is never allowed to crash. This is not what we mean by the “let it crash”. If the Erlang VM process on the operating system ever dies, then it’s time to rethink the design of our system.

I hope it makes sense and I highly recommend to read: http://ferd.ca/the-zen-of-erlang.html

That’s one article that made me really understand the distinction between just letting the whole thing blow up and using OTP + processes to isolate failure.

Cheers :smile:

3 Likes