The path to arrive at the goal is more important than the goal itself.
(Someone said that, probably not Lao Tzu)
Introduction: what we had to accomplish
In ToolTime we have currently three autonomous squads, following the Agile principles of self-organising and cross-functional teams. The squad in which I am working is called "collaboration squad". The name is not only about our internal collaboration, but mainly indicating our focus on enhancing the collaboration, the teamwork and the workflow for our customers.
One of the requested features was to have notifications appearing in the Web application when somebody performs some action. For example, when a field worker (imagine an electrician, a plumber or some other type of craftsman) creates or closes an appointment from the mobile app, the office-located colleagues should see a notification in the Web app.
Here below is a screenshot:
The requirement was just display the notification and don't store it, because we have another feature of the product, called "activity log", to store and display the history of events.
The flow is: a field worker performs some action using the mobile app, then the backend system, that receives requests form the mobile app, should send a notification to the Web app, that in turn should be listening and display the notification for some time.
Different options on the table
We didn't have any notification system in place so far, therefore we wanted to investigate the possible options available for implementing this mechanism. As always happens in these cases, we don't want to just pick any solution, but we want to work together to define the best solution for our problem. All the engineers in the squad, five people at that time, contributed with "spikes" to the team knowledge of available options and practically tried some proofs of concept. The process is very democratic: each one contributes with ideas and proofs of concept, we discuss together and take a decision. We think that only a democratic team environment can encourage people's creativity and we enjoy working this way.
Omitting a few ideas for the sake of brevity, the main solutions we tried in our spikes are:
- Firebase Realtime Database.
- GraphQL subscriptions in our backend service.
- GraphQL subscriptions in a new microservice.
- Server-sent events.
- AWS AppSync.
Firebase was one of the most valuable solutions, as it appeared to be easy to implement, especially on the frontend side. It is just about using a DB table, where the backend should write and from where the frontend should read (subscribing to changes). It could have really become our solution, just we had some minor concerns:
- an additional cleanup mechanism was needed. Our business case was not exactly about storing something, but more of the type fire-and-forget. It looked like a valid solution but a bit "artificial" in our asset.
- There was a lack of "contract" between frontend and backend.
- We should have taken care of some other nonfunctional aspects, like security, in an additional cloud environment, as we are currently mainly using AWS.
Our main backend service is based on GraphQL and the Web app is communicating with it in GraphQL, therefore it looked natural to us to consider GraphQL subscriptions, based on the WebSocket protocol.
The first place we considered for adding GraphQL subscriptions was our backend service, but we soon found two cons in doing that:
- It would have forced us to upgrade the GraphQL version, which is something that we would prefer to postpone according to our plans for improving our system.
- It would have added more responsibilities to the backend service, that we want to split instead, as we are currently migrating towards a microservice-based architecture.
As a consequence, we thought that it would have been better done inside a new microservice, dedicated only to this mission (something like a "notification-service"). Another respectable option, still forcing us to create and maintain a service and to add custom code.
Yet another respectable idea was to use server sent events instead of WebSocket, considering that our case is mono-directional and not bi-directional as a chat. A small reasonable proof of concept was presented for the backend side, still this was making us develop some custom code inside some service for the purpose.
At the end, we embraced the serverless idea, that is AWS AppSync, that is like saying a serverless version of the idea of GraphQL subscriptions, as AppSync is essentially a GraphQL service. AppSync became our notification service.
In AppSync we define a small schema reflecting the structure of our notifications (type of action, author of the action, time in which it took place, etc.), with one mutation for sending notifications, to be invoked by the backend service, and one subscription, to be invoked by the frontend app. We used "NONE" data sources, as the data is just in transit inside AppSync, we don't need to store anything.
Just a few lines of code (such an "if" condition) were added to the subscription resolver, to check that users are subscribing to events belonging to their company and not another one.
Subscribing and getting notifications was easily implemented in the Web application using Amplify. Similarly for the backend service, it was just about sending a request to AppSync with the data about the event that was triggered by some user action.
Nothing to do for the mobile app, just sending requests to the backend service as before.
We had also fun with some amount of work to be done at infrastructure level, not so difficult, regarding the Terraform definition of our resources (infrastructure as software).
Notice: this was an effort only at creation time, to define the infrastructure, but not at maintenance time, as we went for a serverless solution, as mentioned.
The work was related also to exposing the AppSync subscription with a readable public URL, using AWS CloudFront and Route53, and to security.
We don't want to talk in detail about the security aspects of our solution, just for the purpose of security, but we think we obtained a respectable level of security by combining different AppSync authentication modes, for the sender of events and the subscriber, and other general AWS features like WAF.
Here is a diagram summarising our solution:
- The subscription from the Web app goes through Route53 and CloudFront, reaching AppSync.
- A request arrives from the mobile application to the backend service.
- The backend service sends a notification to AppSync.
- The Web app, that previously subscribed to AppSync, receives the notification.
So far, a few months after the release, we have encountered:
- customer satisfaction,
- zero bugs,
- zero infrastructure issues,
- zero infrastructure maintenance work.
The initial research work and the infrastructure configuration paid off. The solution respects the nature of our requirements. The "path" to get to the result and the team collaboration were the key. A mainly serverless solution with a very little amount of code means you can just use it and basically forget it from a maintenance point of you, so that you can focus on other features in this fast-paced world.