Noovolari Smart Backup<\/a> – our product for the management of Backup and Disaster Recovery of AWS infrastructures.<\/p>\nIn this regard, the refactoring of the application\u2019s notifications system, which are shown in the web interface upon the occurrence of specific events (such as the conclusion of a backup job or the opening of a file-level recovery session), is of particular interest.<\/p>\n
The old monolithic solution was based on Ruby\/ActionCable <\/strong>and Redis <\/strong>and centralized the operations for sending notifications in a single application controller.<\/p>\nBy taking advantage of the recent release of the API Websocket for Gateway by AWS, we decided to refactor the notifications\u2019 management engine in a microservice, completely serverless, using SQS to decouple the sending and receiving of messages.<\/p>\n
The introduction of Lambda Layers and the possibility of writing Lambda functions in Ruby (the main language with which Noovolari Smart Backup is developed) is also recent; thanks to these two features we were able to re-use most of the original backend code, thus keeping the code clean and easy to integrate in other future applications.<\/p>\n
This approach has given us several technical ideas that we decided to share in this article, creating a small tutorial on how to create a fully serverless notifications microservice based on AWS services.<\/p>\n
Technologies used<\/h2>\n
To implement this solution we will use the following languages and services:<\/p>\n
\n- Html and JavaScript (jQuery)<\/span>\u00a0\u00a0\u00a0<\/span><\/li>\n
- AWS Lambda<\/span><\/li>\n
- Node.js on AWS Lambda<\/span><\/li>\n
- Ruby on AWS Lambda<\/span><\/li>\n
- Amazon API Gateway<\/span><\/li>\n
- Amazon SQS<\/span><\/li>\n
- Amazon DynamoDB<\/span><\/li>\n
- AWS IAM<\/span><\/li>\n<\/ul>\n
Client-side implementation<\/h2>\n
To begin with, let’s see how to start a WebSocket channel with API Gateway using the URL that will be generated when creating the back-end API.<\/p>\n
In the main application template we added the following javascript function:<\/p>\n
<<\/span>script<\/span>><\/span> $<\/b>(<\/span>document<\/i><\/b>).<\/span>ready<\/b>(<\/span>function <\/b>() {<\/span> \u00a0\u00a0\u00a0<\/span>\/\/Setup notification system<\/span><\/i> \u00a0\u00a0\u00a0<\/span><\/i>noovolari<\/i><\/b>.<\/span>smartbackup<\/i><\/b>.<\/span>notifications<\/i><\/b>.<\/span>websocket<\/i><\/b>.<\/span>connect<\/span>(<\/span> '<\/b><%=<\/b> Rails<\/i><\/b>.<\/b>env <\/b>%><\/b>'<\/b>,<\/span> '<\/b><%=<\/b> Notification<\/i><\/b>::<\/b>NotificationAuthorizer<\/i><\/b>.<\/b>check_token<\/b>(<\/b>user_id<\/b>: <\/b>current_user<\/b>.<\/b>id<\/b>, <\/b>company_code<\/b>: <\/b>current_user<\/b>.<\/b>companies<\/b>.<\/b>first<\/b>.<\/b>code<\/b>) <\/b>%><\/b>'<\/b> \u00a0\u00a0);<\/span> <\/<\/span>script<\/span>>\r\n<\/span><\/pre>\nThis is how the function is implemented in detail:<\/p>\n
noovolari<\/i><\/b>.<\/span>smartbackup<\/i><\/b>.<\/span>notifications<\/i><\/b>.<\/span>websocket<\/i><\/b>.<\/span>connect <\/span>= <\/span>function<\/b>(stage, token) {<\/span> \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>var <\/b>socketUrl <\/span>= <\/span>'wss:\/\/<\/b><WEBSOCKET_URL><\/b>\/'<\/b>+stage+<\/span>'?token='<\/b>+token;\r\n\r\n <\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>\/\/ connecting to the websocket url and retrieve function for connect and disconnect\r\n<\/span><\/i> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><\/i>noovolari<\/i><\/b>.<\/span>smartbackup<\/i><\/b>.<\/span>notifications<\/i><\/b>.<\/span>websocket <\/b>=\u00a0 <\/span>new <\/b>WebSocket<\/i><\/b>(<\/span>socketUrl<\/span>);\r\n\r\n <\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>noovolari<\/i><\/b>.<\/span>smartbackup<\/i><\/b>.<\/span>notifications<\/i><\/b>.<\/span>websocket<\/i><\/b>.<\/span>onopen <\/span>= <\/span>function <\/b>() {\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>console<\/i><\/b>.<\/span>info<\/span>(<\/span>'socket connection opened properly with...'<\/b>);\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0};\r\n\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0<\/span>noovolari<\/i><\/b>.<\/span>smartbackup<\/i><\/b>.<\/span>notifications<\/i><\/b>.<\/span>websocket<\/i><\/b>.<\/span>onclose <\/span>= <\/span>function <\/b>() {\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>\/\/ websocket is closed.\r\n<\/span><\/i> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><\/i>console<\/i><\/b>.<\/span>info<\/span>(<\/span>\"Connection closed...\"<\/b>);\r\n\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0};\r\n\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0<\/span>noovolari<\/i><\/b>.<\/span>smartbackup<\/i><\/b>.<\/span>notifications<\/i><\/b>.<\/span>websocket<\/i><\/b>.<\/span>onmessage <\/span>= <\/span>function <\/b>(evt) {\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>var <\/b>notification <\/span>= <\/span>JSON<\/i><\/b>.<\/span>parse<\/span>(evt.<\/span>data<\/b>);\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>var <\/b>message <\/span>= <\/span>notification<\/span>.<\/span>data<\/b>;\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>noovolariUiToolkit<\/b>.<\/span>notifications<\/i><\/b>.<\/span>subscribers<\/b>.<\/span>forEach<\/span>(<\/span>function<\/b>(subscriber) {\r\n<\/span> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>if<\/b>(subscriber.<\/span>