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 Controller
calls a function from SurfaceLogic
module. And this function calls another one from DeepLogic
module. DeepLogic
module works with some CRUDs and external APIs through clients.
#DeepLogic module
def do_deep_logic() do
with {:ok, response} <- ExternalApiClient.get_info(), # can return {:error, :api_500_error}
{:ok, _} <- CRUD.insert() # can return {:error, changeset}
do
{:ok, do_some_logic(response)}
else
{:error, %Changeset{ ... } = changest} -> {:error, :already_exists}
{:error, reason} -> {:error, reason}
end
end
# SurfaceLogic module
def do_surface_logic() do
#pattern match on {:ok} and just pass all the errors to the upper level:
with {:ok, value} <- DeepLogic.do_deep_logic() do
{:ok, do_some_response_preparations(value)}
end
end
#Controller
def handle_post_request(conn, _) do
case SurfaceLogic.do_logic() do
{:ok, result} -> render( ... ) # render success
{:error, :already_exists} -> render (...) # render already exists
{:error, :api_500_error} -> render ( ... ) # something wrong with external data provider
end
end
So now I need to have case
/with
on every single layer here. (Yes, I still have action_fallback
on 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
/with
statements 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 CRUD
and ExternalApiClient
and catch them on the controller level. In this case there wouldn’t be a need in case
/with
on the level of DeepLogic
and SurfaceLogic
. But it seems like this way is more uncommon in Elixir/FP community.