The Phoenix framework has a built-in facility to manage two way communication between web clients and the server called channel.
Clients of Phoenix channels can be regular erlang processes or, more interestingly, external processes. In fact, there are a bunch of clients written in different languages that allows to communicate via Phoenix channels from web and mobile applications.
I’ve had the chance to use the dn-phoenix C# client that I am considering using in the Xamarin application that I am currently working on, more on this library further.
Channels abstract the underlying two way transport mechanism while offering a programming model based on the pub/sub messaging pattern.
In this post, I am going to lay out the important parts of this great feature but first a couple of words about two way client server communication.
two way communication
Historically on the web, communication between clients and servers was always happening on a request/response basis; the client sends a request and the server returns a response. There was no way for the server to push data to the client without the client asking first.
In the last decade (or two), requirements for interactive web application that are able to refresh displayed content in soft real-time showed up such as online multiplayer video games and social networks.
Phoenix channels, as pointed out earlier, abstracts away the transport technique used for two way communication, it default to using web-sockets and can fallback to a technique called long-polling when the former is not supported by the target web client.
essential phoenix channels concepts
Channels, as stated in the documentation, have some moving parts, consider the following chart:
The previous diagram shows the components involved when using channels and how they interact. Let’s start off by the client.
Channel clients are usually external processes that uses a channel’s client library in order to join a specific topic in order to send and receive messages to/from the server.
They typically establish a web-socket connection to the server identified by a url similar to
ws://localhost:4000/socket and then specify a topic to join.
The channel client api offers a way to send messages and to register callbacks that are executed when a specific message is received from the backend server.
The following listing shows an example usage with the C# dn-phoenix client:
The dn-phoenix client library seems pretty solid, it can handle the previously described capabilities as well as connection reestablishing when a transient disconnect happens.
The only catch is that it is implemented with the Full .NET Framework and does not seem to work on Xamarin yet.
Topics in a pub/sub context represents a common interest to which individual processes can send/receive messages to/from.
In a typical pub/sub scenario, we can have multiple processes subscribed to a topic. Each time a process sends a message to the topic, the message is forwarded to all the subscribed processes.
When using Phoenix channels, clients usually subscribe to a specific topic by calling the
Topics are mapped to specific a channel module in the socket configuration.
Messages in the channel context, are defined by a name (also called an event) which is usually a string and they carry a payload which is usually a JSON object.
The payload is belongs most of the time to a dictionary or list type that is serialized when sent and that is de-serialized when received. The channels api makes this process very seamless both on the server and on the client side.
Channels are actually modules that uses the
Phoenix.Channel definition, they provide three basic interface functions, as you can see in the following snippet:
joinallows to handle a client trying to join a topic, usually some form of authorization is performed in this function.
handle_inallows to specify what happens when a message arrives from the client, typically this function would broadcast the message to the other channels instances that joined the same topic.
handle_outused with the
interceptmacro is used to capture messages that are going to the client’s direction, this function usually decides whether or not to forward the message to the external client.
Channels are actually OTP GenServers you can see in the previous diagram that a channel instance is created for each client joining a topic, the
socket argument is the actual
state maintained by the GenServer and we can use it to store arbitrary data.
We mentioned that a client joins a channel by establishing a web-socket connection to specific url. Web-Socket connections in elixir are usually targeted to specific path which is configured in the
socket sub-path is mapped to the
The socket module is usually straightforward it just contains to key pieces:
- The topic to channel mapping
- The connect function that can act as a callback for inserting behavior when client tries to connect.
Phoenix.PubSub module tracks down subscriptions to topics and takes care of forwarding messages between channel instances, these instances can be distributed over different BEAM instances and the PubSub system is able to handle that.
After playing around with basic channels on a erlang single node, I am looking forward to see what kind of challenges arises when working with multiple nodes.
I heard about the Phoenix presence module that provides solutions for inter-node consistency problems; also looking forward to play with that when the need will come, in my project, to scale to multiple nodes.