ravicious

ravicious

Tips for building resilient frontend apps which use Phoenix channels for backend communication

When you develop an app which communicates with the backend over HTTP requests, handling unsuccessful requests is straightforward: you send a request and you get a response. Based on the response you can detect whether your request was valid or if the server crashed as a result of it.

With Phoenix channels, it’s a bit different. You still send a message and get a response back, but if your message crashes the channel, you don’t get the response and your only hope is the onError hook, which most probably lives in a different place than the code which sent the message.

As of now, our frontend app keeps the knowledge about the state of the channels it uses (whether the channels have been joined or if they crashed). If one of the channels crashes, the app replaces the whole screen with a big spinner which makes the user wait until the connection is reestablished.

The app has to do that, because the library for handling submitting forms is based on promises. If the message (which was a result of submitting the form) crashed the server, the promise for sending the form data will never get resolved. In this case, we have to destroy the form component to reset its state (otherwise it’d be stuck at “submitting” state).

Now, I’m not particularly fond of this solution. Mainly because the onError hook is called not only when the channel crashes, but also when the connection is abruptly stopped – that happens very often, especially when you’re on mobile and you open the app in the browser and then close the browser and open it again.

In short, I’d rather have my app to deal with errors when they happen as a result of user action (like in the HTTP example) rather than having a big wooly error handler (the spinner) for the whole app. I don’t want to disrupt the user experience if the connection is temporarily dropped and I still want to precisely handle server failures, but I don’t quite know how to approach that with the available tools.

Do you have any tips on how to design frontend apps to handle such error cases? Should I change my thinking and get over the fact that the semantics of HTTP requests and Phoenix channels are vastly different? :wink:

I already read the docs about channels and the source code of the frontend client.

Most Liked

aseigo

aseigo

In such cases I tend to split these kinds of things into two pieces: the part that does the communication to the client, and the part that does the business logic.

I put each in their own process, with the idea that if there is any failure in the communication process (the channel, in this case) it must mean something went quite wrong with the communication link (e.g. the network) itself and there is not much that can be done about that. The client must always be prepared for such events, so in case of failure, that’s ok.

Then the business logic part, which is where it is more common to experience bugs and recoverable errors, is run in a process separate, and therefore isolated from, the communications part.

The business process (in your case the form processor) is then monitored by the communications process (using the process monitoring that comes bundled with OTP) so if anything goes wrong there, the communications process can report back to the client or, if appropriate, try again. Often I allow some sane number of failures such as 3 before completely giving up. This also gives you a place to log the error safely, perhaps even storing the raw request for later examination (e.g. for bug fixing purposes).

This also has several additional nice side-effects: The business logic process can operate sequentially / synchronously, and the comms process can be updating the client with ping’s to keep the connection alive even when the process takes a long while. The business logic process can bang off progress messages to the comms client which can pass them on appropriately. The communications process can handle multiple requests in parallel (by spawning more business logic processes). etc, etc.

Sooo … tl;dr -> considering splitting out your business logic from the channel itself and run it in its own process :slight_smile:

hubertlepicki

hubertlepicki

This is interesting. I would love to see some code / read blog post… :wink:

outlog

outlog

when you push to the channel you will receive ‘ok’, ‘error’ or ‘timeout’ much like http, so that api call in it self is pretty similar.

callApi(apiName: string, body: any): Observable<any> {
  return Observable.create((observer: Observer<string>) => {
    if (this.channel) {
    this.channel.push(apiName, { body: body || {} }, 5000)
      .receive('ok', (msg) => { observer.next(msg); })
      .receive('error', resp => {
          return observer.error(resp);
      })
      .receive('timeout', () => {
        return observer.error('timeout');
      });
    } else {
      return observer.error('no_channel');
    }
  });
}

How you handle the errors is also similar and depends on the UX, for GETs I usually do auto retry on error/timeout and show non blocking UI(inline spinner or what not), but the user can still nav away (of course cancel the retrys in that case). For POSTs I show an alert that there was an error and that the user should retry.

Handling the channels should be automatic in my experience - I don’t handle onError for the channel, same for the socket. Once you are connected they should reconnect if anything happens. If I crash the channel the client just rejoins almost immediately.

It of course depends on your app, a live game or data stream - you would start to handle the channel down scenario - but for these ‘api calls’ on channels the above works fine, and is handled identically to http requests.

I usually build it so you can easily swap an api call and chose to run it over http or websockets, just imagine the above function as callhttpApi, it would look pretty much the same.

So you can use channels in a very similar way as http requests.

Of course the real fun starts when you push data from the server, but all that depends on the client implementation and how data is used/stored/rerendered etc.

Where Next?

Popular in Questions Top

mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: &lt;h1&gt;Create Post&lt;/h1&gt; &lt;...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29305 241
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
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52238 488
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New

We're in Beta

About us Mission Statement