A WebSocket Chat Microservice in Java
This article describes how to create a high-performing chat application that functions as a microservice using Java and the Baratine framework.
Join the DZone community and get the full member experience.Join For Free
- Building .jars not.wars
- Asynchronous nonblocking methods
- Pub/Sub functionality
- Performance & Scalability
- API/interface based programming
- Single-threaded frameworks as a solution for multi-threaded concurrency issues
For reference, the most recent DZone article that detailed a WebSocket chatroom can be found here: https://dzone.com/articles/creating-websocket-chat. While the article was a great starting point for people to get familiar with WebSockets, this chat application will demonstrate how open source framework, in particular, Baratine, can help developers implement the best current known practices in the industry.
Running the Example
To run the example, do the following:
- Ensure that you have installed the Gradle build tool
- Clone the repository from: https://github.com/swily/WebSocketChat.git
- Run gradle build
- Run gradle run
- Open your browser and navigate to localhost:8080
Overview of Files
While the application includes a few POJO classes, the majority of the work is done in the following three files:
index.js / index.html – Displays chat messages and chatroom
ChatWebSocket.java – Listens to the websocket wire, connects the PubSub message pipes
ChatService.java – Support for long-polling, starts the Baratine server
ChatWebSocket relies on Baratine’s implementation of the WebSocket protocol. In order to have access to the WebSocket wire, we implement ServiceWebSocket with our parameterized type Message. Message is our own defined class of what we expect to be coming across the WebSocket wire.
Beyond listening to the WebSocket wire, we also set up our PubSub messaging inside of Baratine. PubSub in Baratine has three components:
- A PipeBroker
- A Pipe Subscriber (PipeSub)
- A Pipe Publisher (PipePub)
You’ll notice that we also make use of a _messagePipeHandler to ensure that we can properly implement "close" functionality when a user leaves the chat room.
Setting up of the pipe is done in lines 88-93 in the following order:
- Create a PipeSub endpoint to tell where messages should flow out of the pipe to—in our case, I want these messages passed to my OnPipeReceive method. I utilize a lambda to achieve this.
- I subscribe the instance of my PipeBroker to the PipeSub endpoint.
- I create a handler to close the pipe when the connection is closed.
Here is the setup of the pipe:
ChatService is responsible for creating ChatMessages and passing them into the pipe. The methods are fairly straightforward; however, while scanning the class three important pieces might jump out:
- Annotations such as @Query on the send method provide long-polling fallback for HTTP. Methods can be called directly over WebSocket by clients, or accessed over HTTP.
- Our main method uses the Baratine specific (Web.include(foo.class)) in order to provide the Baratine server with access to these classes.
- Baratine’s Result is used as an asynchronous callback so that methods do not block. Instead, a method finishes processing when given the .ok() call.
We start our Baratine web server with a simple Web.start()method as such:
This microservice was benchmarked on a MacBook Air Core i7 3667U with the following results:
20 million messages/second/instance with no acknowledge messages
9 million messages/second/instance with acknowledge messages
6 million messages/second/instance with full ping-pong acknowledgment on every message
Baratine is able to achieve these results due to its automatic batching and underlying single-threaded implementation. What’s more is that the @Service annotation on a class provides a lock-free and ordered inbox for processing messages on a service. Because of this, Baratine does not need to have synchronized code blocks for concurrency; data is only ever accessed by a single service owning thread, thus making dreaded concurrent access a thing of the past.
In fact, because of this improved underlying programming model, Baratine can persist data to internal data stores without the need for a data schema or an outside data source. Baratine does have MySQL drivers for the database you have already licensed, but many applications and services can rely on operating with their data in-memory and persisting to Baratine’s internal data store when needed.
Baratine 1.0 is officially released under the GPL license and offers a significant improvement over many of the issues that plague and prevent agile development in today’s programming world. With Baratine, developers can simply code an API and know that the implementation will be ready to be consumed by today’s IoT clients from any language (JSON permitting). This application can be embedded into an existing Java EE project or used as a standalone service. This is the promise of microservices as features to an application—they can be added and removed without an overall impact to the current architecture.
While you may or may not need a specific chat implementation added to your current web app, I am hoping that people will take away the structure of the underlying architecture in this example application and apply it to their specific use case. Feel free to fork the GitHub instance and continue the development!
Opinions expressed by DZone contributors are their own.