verkhovin
Hi guys!
Want to open here another discussion about error handling concepts in Elixir because it looks like I really stuck here by myself.
My question is about errors that are related to users interactions, where you can’t just “Fail fast and forget / Let it crash” but need to respond with some appropriate error message.
Elixir community’s standard is to return tuples like {:error, reason} when something goes wrong during function execution. Overall, it looks good at the first sight.
But at some point, I noticed that to handle this approach I have to write tons of case or with really often just to check that underlying call didn’t return this {:error, reason} tuple.
Consider an example of a multilayered application (like Controller → Business Logic → database CRUDs). If there is some expected error that occurs on the layer of CRUDs, I will need to have a case to catch this error on a Business Logic layer and pass it to Controller. After that, I need to have another case on the Controller’s layer. And it becomes even worse if you have more than 3 layers there, or your business logic includes something more than just a call to CRUD layer.
Maybe I really missed something but it seems to me that this approach makes you write a lot of boilerplate.
I’m actually from Java world and it would be quite natural for me to resolve this issue by throwing (raising) an exception from the CRUDs level and catch in on the Controller level. But if I understand correctly, exceptions in Elixir philosophy are more about unexpected errors that should be handled by this “Let it crash” way.
My question still is: is it normal and common way to write elixir code having this error checks (case/with) on every layer of the application?
I can provide some naive code example if my thoughts are not clear.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
hauleth
I would say that it depends on the requirements and what kind of error is that:
Plug.Exceptiondo it’s thingkokolegorille
What You don’t have in Java is pattern matching and functions with multiple heads.
The first condition can often be solved with a function pattern matching some kind of result. ok, error is quite common in FP.
But if You are sure the operation cannot fail, You can use the ! operator.
It’s a good practice to push DB related to the outer boundaries. It’s also possible to use onion architecture. Where the central part is purely functional, and does not care about details of the DB layer.
The let it crash means something different. In case there is a problem You cannot predict, You are covered by the supervisors.
joddm
I think business logic and CRUD operations are usually handled by Ecto Changesets or multiple function heads which give an error tuple when applicable. Then you can handle any errors in the controller with a
casestatement as you’ve said.verkhovin
Yes, I kind of feel this the same way.
I saw opinions here on the forum that throwing exceptions is some kind of “dirtier” solution than returning ok/error tuples. Maybe I misunderstood it somehow
Thank you for the answer!
verkhovin
Thank you for your answer!
I was just wondering if it is ok in FP world to pattern match almost every function call result to determine erroneous responses and handle them. As you said, it is quite common approach, right?
If I understand you correctly, your point is that I can replace
case/withwith functions with multiple heads.I think it is still the same boilerplate but in another form. Maybe I am too dramatic about this boilerplate issue by the way
verkhovin
Thank you for your answer!
Sometimes it is not the case just to map response from Ecto directly up to the controller.
For example, you can have a chain of calls of functions from different modules. And each of these calls returns this ok/error tuple. You can’t just pass them all to the controller and the only option here is to pattern match each of these responses with
withexpression. So you handle it on this level in this way and then return ok/error tuple to the controller. And in turn, in the controller, you need to do the similar pattern matching again.Hope it is clear.
sezaru
I’m not sure if I follow you when you say you cannot redirect them all to the controller.
Let’s say you have a function
CRUD.getthat returns{:ok, value}or:error, reason}and that reason can be multiple types of errors.Now let’s say you want to redirect the errors to the controller from your business logic and handle the
:okcase, if you redirect something to the controller via this function return, you can simply do something like:This will handle the
:okcase and redirect all the other ones.If you need to call a function to redirect it to the controller, you can do something like:
or
In all these cases you don’t need to have a case for each error tuple, you can simply pattern match all of them.
Now at the Controller, if you want to handle each one of the errors, then you will need to use
caseor function pattern match to handle it, but that would be the same with java handling the catch cases.Doesn’t that solves the issue or am I missed the point entirely of what the real problem is in your case?
verkhovin
Thank you for you detailed answer!
Yes, this is almost what I was talking about. I’ll provide an example to show what I meant.
Let’s say that CRUD operation is not the only thing I need to do during the request handling. And let’s say there are more than 2 layers in the application. So
Controllercalls a function fromSurfaceLogicmodule. And this function calls another one fromDeepLogicmodule.DeepLogicmodule works with some CRUDs and external APIs through clients.So now I need to have
case/withon every single layer here. (Yes, I still haveaction_fallbackon controller level, but it will just move matching from this controller to Fallback Controller, so I’ll leave it here now to make it easier to understand).My point here is that all these
case/withstatements look like boilerplate that you need to have everywhere (on every single function call, if this function returns:ok/:error). And I just try to understand if it is common practice (considered as ok) or not.A more natural way to handle this issue for me would raise exceptions on the level of
CRUDandExternalApiClientand catch them on the controller level. In this case there wouldn’t be a need incase/withon the level ofDeepLogicandSurfaceLogic. But it seems like this way is more uncommon in Elixir/FP community.soup
How you’ve written it is how I would have probably done so also, but I can’t speak as a community luminary.
Related: Good and Bad Elixir which may give you a bit of peripheral insight at least.
Specifically “Don’t pipe results into the following function”, which you maybe could have used in surface logic, i.e:
But as you say, it’s only really shuffling stuff around.
See also “Raise exceptions if you receive invalid data.” in the same article.
By it’s nature, the article has to be opinionated but it’s definitely worth reading once or twice, “know when to break the rules”, etc blah blah blah.
I think the existence of
{:ok | :error}and the line “In practice, Elixir developers rarely use thetry/rescueconstruct” in the getting started guide gives the impression that exceptions should be avoided in Elixir, but they exist to be used and is probably a few good blog posts waiting to be written re: navigating between them and tuples.verkhovin
Thank you for the link. The article seems really useful! Going to read through it a couple of times.
Yes, I think this is exactly what makes me so confused about it