19 December, 2019
What is the first thing that comes to your mind when you hear “real-time web apps”? Yeah, it’s WebSocket. Notifications, messaging systems, real-time changes to your interface – all of those are the most common usage examples for this kind of communication. It’s relatively easy to be handled as long as you’re not using it for complex operations. But what if you want to build an app using ONLY WebSockets? Well, it’s a different story.
It all starts with a project…
Not that long ago, we were working on some new projects at The Software House. All of them had something in common. Namely – communication protocol. Those systems required a real-time protocol, that’s why instead of using obvious choice for our API like HTTP + REST, we had to go with a new one – WebSockets. Socket.IO to be precise.
It’s common to use WebSockets for features like notifications, chats, messaging systems and so on. In most cases – you’ll end up with some WebSocket gateway that broadcasts messages to all connected clients or only a set of them.
In fact, it’s mostly one-way communication. Messages are sent from the server to connected clients. So, what will change if you decide not only to send messages but also receive commands through WebSocket?
REST architecture is probably the most common way to implement your APIs. It’s standardized, easy to master, well-known and popular. Sometimes, you can even forget how useful it is.
What if I tell you that REST helps you a little bit with keeping your architecture cleaner? It sounds like a lie, but it’s not.
When you implement REST – each endpoint is responsible for a single operation based on the HTTP method and URL.
Endpoint POST/users is responsible for user creation, while GET/users returns a list of users. That’s pretty simple, right?
When it comes to implementation, most of the API frameworks will force you to separate each endpoint. A good example is Express. It’s probably the most popular Node.js API framework. Even though it does not include popular solutions like MVC or ADR, it still makes you write cleaner code. Do you wonder how? Let’s take a look.
This is a typical POST endpoint definition. What’s important here is the last part. Each endpoint has its own implementation function. In a presented case, it’s called userCreateAction. REST by design doesn’t have a single entry point that is responsible for handling operations. On the other hand, WebSocket is something else.
Single (entry) point of failure
WebSocket doesn’t make it easier for you to create extendable and maintainable apps.
All messages are handled in a single place by default.
It’s just okay for simple notifications or broadcasting services. But what will happen when you introduce commands? Let’s image that you want to execute some operations based on user messages.
If not handled properly, your code will become cluttered. Even if you move business logic to services, you will still have those if statements. So how can you solve it?
Message as a contract
The first thing you need to change is the structure of a message. Instead of sending random data, you may treat them as a contract. This is something common in RPC communication protocols, where you not only specify the input data but also define what type of operation you want to perform. A well-structured message might look like the one below.
Each operation should have a single handler assigned to it. For example, if you want to create a new game, then you should send a message with a create-new-game operation type and also implement a corresponding handler.
The next step is to remove the if-statement hell from your WebSocket message handling code. The contract-handler pair allows you to easily switch from conditionals to a Set.
An operation type is a key in your object, while its handler is a value. Of course, you should also check if an operation is supported. This might be handled by a simple class.
Finally, you can refactor your if-conditions to a much more readable piece of code.
But here is still a single issue. How do you handle sending a message to the client from a handler? Should handler know about connected clients or maybe it should return something?
Instead of making your handlers coupled with WebSocket, you should think of how to keep them separated, so you can also use them for a different communication protocol.
A very useful decoupling method is to introduce Events. When one of your handlers is ready to send something back to the client, it should publish an event on an internal messaging system.
Any piece of code that is interested in handling that event, should listen for it. For example, you may have a specialized code that will listen for it, convert to message and finally send it to a connected client.
Such communication could be implemented using EventEmitter or Subject from RxJS.
The only thing you need is an instance of a publisher injected into your handlers.
Then, you are free to publish as many events as you need, directly from our handlers.
Those should be listened by a subscriber, that will be responsible for converting those events to WebSocket messages and sending them to clients.
I hope that all the code fragments, tips and tricks presented above are pretty straightforward. As you can see, a few simple changes in your approach may make your WebSocket code extendable and much easier to maintain.