This article describes how to create a high performing chat application that functions as a MicroService. Within this MicroService, I will be using Caucho Technology’s Baratine ( v 1.0) framework to demonstrate the following:
- 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, majority of the work is done in the following 3 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 chatroom.
Setting up of the pipe is done in lines 88-93 in the following order:
- Create a PipeSub endpoint to tell when messages should flow out of the pipe, 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 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 finishing 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 acknowledgement 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 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 for their specific use case. Feel free to fork the github and continue the development!