Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Great article, very much appreciate this guy and his blog as I continue to learn Erlang.

A question: the pattern of let it crash makes sense to me. However, I struggle with it when implementing RESTful web services. Letting the process crash will typically yield a 500 - the monitor on the connection process in the web server library ensures that. Clearly though, a status code and some additional information is a more appropriate response to the client. An example is returning a 400 for "missing arguments". In a contrived example of idiomatic Erlang, I feel like I'd write this:

    handle_request(Request) ->
        % username and password are required
        {ok, Username} = request:get_argument(<<"username">>, Request),
        {ok, Password} = request:get_argument(<<"password">>, Request),

        UserRecord = db:find(my_database, users, #{<<"username">> => Username}),
 
        Password = maps:get(<<"password">>, UserRecord),

        request:respond(200, UserRecord, Request).
And if the key username did not exist in Request, request:get_argument/2 would return undefined or {error, Reason}, giving me a bad match error. Or if the password didn't match, I'd get the same. In order to intercept that and return a reasonable status code, I would have to catch that in handle_request. My question, then, is am I missing a best practice on how to handle this? Or is this just the place where I do need to catch errors and process them? And if that is so, isn't it at odds with the whole concept of writing intentional code?


A couple of hints:

1. Use a finite-state-machine REST framework like webmachine or cowboy_rest. This will help you in the long run once you grok how they work.

2. Your intention here is that the user might have done something silly. Write a helper which can load arguments from the request and fail if some of them are missing. The best approach is to shuffle as much as possible into a routing layer and then let the routing layer return the 4xx responses. This only leaves up optional arguments, where an undefined option is what you want to handle. Look at how, e.g., cowboy is doing this.

You can essentially avoid all of this boilerplate, if you construct your HTTP RESTful API correctly.

db:find/3 should probably return either {ok, UserRec} or not_found. Something along the lines of

  case db:find(my_db, users, #{<<"username">> => Username}) of
    {ok, #{<<"password">> := Password} = UserRec}} -> respond(200, ...);
    {ok, UserRec} -> respond(401, ...);
    not_found -> respond(404) % or something more appropriate
  end.


Thanks for the followup. Item #2 is a point very well taken and I'll be exploring that route, appreciate the response.


Or is this just the place where I do need to catch errors and process them? And if that is so, isn't it at odds with the whole concept of writing intentional code?

No it's not. The author touches on this:

Note the word intentional. In some cases, we do expect calls to fail. So we just handle it like everyone else would, but since we can emulate sum-types in Erlang, we can do better than languages with no concept of a sum-type.

So handle errors you expect explicitly and let the rest crash.

    case user:authenticate(Username, Password) of
        {ok, UserRecord} -> ...;
        {error, notfound} -> ...;
        {error, badpass} -> ...
    end,



Think about the client point of view. It is much better to return "401 Unauthorized" or some other reason rather than 500 .

i would do a case on db:find and proceed to check password if you get user or fail with 401 and same with password case and if its correct go on and if not 401 :)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: