How we build Bytebase — Part Two: Extending Django to support Real-time Apps with Django Channels
Part One covered why we chose WebSockets and how they work.
This post will cover:
- the motivation for Django Channels
- the core building blocks for Channels
- how to build our first chat server by following the official Django Channels tutorial
If you don’t know us, Bytebase is the byte-size knowledge base for engineering teams. Bytebase supports collaborating on a module in real-time.
In this post, we share how we used Django Channels to enable our web application for Real-Time communication.
How Django Channels Works
Django Channels is a project that extends Django to work across any protocol, not just HTTP. Channels extends Django by adding an asynchronous layer to support long-running connections.
Without Channels, Django code is synchronous.
Synchronous code is executed one line after the next. Any pauses in execution do not provide an opportunity for other code to run.
Asynchronous code can pause and let other code take over. It allows for concurrency, which is the ability to run in parallel.
Sync vs Async
To understand the difference between synchronous and asynchronous code, consider this example, adapted from this Introduction to Async Programming.
Suppose Alice is a chess player and needs to complete two chess games. If Alice plays synchronously, then she plays one game to completion, and then play the next one. While Alice’s opponent is deciding on her next move, Alice is waiting.
If Alice plays asynchronously, she can jump between games. While Alice’s opponent is deciding her next move, Alice can make a move in a different chess game.
Django Channels enables asynchronous support for Django applications. This makes applications more performant when it comes to I/O and waits.
Our chat app will leverage this asynchronous capability, where a WebSocket client is always open and waiting for new messages to arrive.
Why we need Async
Asynchronous capabilities are a pre-requisite for supporting the WebSocket protocol. While one or more long-lived WebSocket connections are open, our web server needs to listen for incoming messages. We don’t want the server to block while listening, so we require our server to be asynchronous.
Core components of Django Channels
Django Channels adds an asynchronous layer to Django applications. It lets Django programmers use the same concepts they’re already familiar with, and adds the following:
A Consumer is similar to a Django View, but it’s built for both synchronous and asynchronous code. It’s called a “consumer” because it consumes events.
Here’s a sample consumer from the official Channels Tutorial. The Consumer defines behavior for connecting to the client, disconnecting, and receiving messages from the client.
With this consumer, you can create messages and view them in your browser. You cannot yet communicate with other clients. See code for this behavior.
We want to have real-time communication with other clients too. To do so, we need to add a Channels Layer.
The Channels Layer is a backend that enables clients to talk to each other.
A Channel is a mailbox where messages can be sent to.
A Group is a group of related channels. You can send messages to all Channels in a Group.
A Channel Backend is a persistent store that’s responsible for:
- Storing the groups and the channels that belong to them.
- Routing messages to the right groups.
We use Redis as our channel backend.
Using Django Channels for real-time communication between clients
First, we’ll update our consumer to send messages to all channels in a group. It will still send messages to itself too.
Next, we’ll configure Redis as our backend. We use the channels_redis library to configure Redis as our backend. To do so, we add this configuration in settings.py.
Now we have consumers that can talk to other clients and we have a redis backend that knows how to route messages between clients.
Check out this code to run it yourself.
Thanks for reading!
Bytebase, the byte-size knowledge base.
Bytebase is a web app that helps engineering teams share knowledge better. Everything in Bytebase is a “byte” — a short chunk of information. Bytes can be grouped together and organized, using the same principles engineers use to organize their code.