How we developed the notifications service
In this article, I will tell how we developed the notifications system, which was recently launched in VMmanager (our server virtualization management platform). I will focus on system architecture and implementation details: which technology stack was used, which decisions were made and why, and what happened in the end. I will also tell you how dynamic data updates are arranged in our single-page application.
To implement a user notification service for various event types, e.g. an unfinished task, resource overuse or low account balance.
Key requirements to the service:
- Easy integration with existing company products
- Ability to work with different sources of events
- Sending events to different channels (email, interface and messengers)
As a result, we should get an easy to use framework: it should be sufficient to connect it to your project and specify the business logic of the necessary processing modules in any convenient programming language.
Here’s what we have done
To reduce the number of reader's questions about why certain decisions were made, let us review the architecture of existing ISPsystem products. Namely, we are interested in the part that is responsible for sharing updates between Backend and Frontend.
All our products are single-page applications, where a service called Notifier is responsible for dynamic data update.
Here is its operating logic (diagram 1):
- The user opens the website page.
- Frontend (FE) via websocket (WS) subscribes to events of certain entities from Notifier. In our case, it subscribes to all virtual machine (VM) events.
- The user creates a VM.
- Backend (BE) starts the VM creation task.
- BE compiles a notification for the Notifier about creation of the virtual machine.
- Frontend learns about the new VM through WS.
- The user sees the new VM in the list of virtual machines, which has the "Installation in progress" status.
Interestingly, Frontend not only learns about the event, but also receives full information about the entity.We have simply removed the extra link from the FE chain:
subscribe → get the update →
request to BE → display the object
At the time of subscription, Frontend provides to Notifier the endpoint, which needs to be requested when the update is received. This way, FE gets the complete element with the update. As the result, we are saving time on the query.
The result is provided shown in diagram 2. Let us review it.
This is the input point, where notification settings will be entered. It is a microservice, whose functions include:
- To accept the settings consisting of a number of mandatory fields and additional information in free format.
- Release the list of settings by the final alert name.
- And process two or three endpoints, which meet the CRUD paradigm.
Input data format:
name — final alert name for filtration.
metric — the object, which is included into the final alert unchanged. It contains data in random format, which are different from one alert to another.
channel the object with channels to which notifications need to be sent. Each channel contains recipients, e.g. chatID for telegram or list of IDs for email notifications.
enabled — the field controlling whether the setting is enabled.
Final alert wrapper
This is an executable file that starts the final alert, reads its output stream (stdout) and writes to the input stream (stdin). It takes over all the work with the configs and entities required for operation of the alert, as well as sending messages to the notice-center.
Let us take a look at the basic principles of the wrapper using the example of the sequence diagram.
The configuration file and the final alert are required to start the wrapper. The path to the executable alert file and entity, with which it will work, is specified in the config file.
The wrapper works in three main streams:
- Main — this is where we read the config file at the wrapper start. We launch the alert, while getting its stdin and stdout. In addition, we start the following two streams.
- AlertWatcher is the stream, which, having received the alert’s stdout, will track it. If data is received, they will be processed and sent to the notice center.
- NotifyWatcheris the stream tracking information update in notifier. If updates are received, they will be processed and sent to the alert’s stdin.
Why make everything so complicated, you may ask. The fact is that wrapper is designed to facilitate the work of a person or a team that will implement the notification service. This way, we accomplish the main target — easy integration. Wrapper assumes the following list of functions:
- Tracking changes in data, with which the alert works. Therefore, it provides consistency of data between the settings service, panel and the alert.
- Notifications processing and sending.
It will be important to be able to write an alert in any programming language suitable for a specific task. All there is left to do when writing the final alert is to:
- Process the config file sent by the wrapper to stdin.
- Implement the business logic of the alert itself
- Feed data to stdout in the correct format
As I mentioned above, Notifier acts as the data provider needed for the wrapper and the final alert to work properly.
Alert wrapper subscribes to updates:
- Configurations (alert configurator);
- Entities, which the alert will process (panel);
- Message recipients (panel).
This way, consistency of data both within the wrapper and within the final alert is ensured.
Message sending center (msg sender)
A ready-made tool that allows our panels to send mail to email users. The original idea for the center was to be able to send messages not only by mail, but also to any necessary channel: telegram, slack or panel interface. However, later it was decided that the user should be notified in the interface, so a notification center appeared between the alert wrapper and msgender.
This is the service in charge of storage, forwarding and display of notifications to the user.
- Storage. Notifications received from wrapper are saved so that the user can review their history.
- Display. When a notification is received, the notice center shows it to the user as a push message. If the user clicked on the message, it is marked as read.
- Forwarding. If the previous condition was not met, the message will be delivered to the msg sender after a certain interval of time and sent through the necessary channel.
The resulting service is the additional channel of communication with the user, the single storage point for all notifications and the message gateway, which takes some of the load from msg sender.
As a result, we have a functioning service to send notifications to users. At the same time, individual parts of it can be used outside the alert+wrapper context, you can send messages directly to the notification center. Msg sender can be further expanded by adding support for different channels.