Introduction
In my previous blog post, I talked about the technologies and tools I used to create DoNotSkip.
In this blog post, I'd like to describe the overall cloud architecture of DoNotSkip.
Cloud Architecture
Below, the big picture of the cloud architecture. Many things are not represented in the schema, but it gives you an idea.
- Firebase
- Hosting: Serves static files such as HTML files, CSS files, JavaScript files, images, etc. with a global CDN.
- Storage: Stores coaches' assets such as images. These assets are only available to those uploading them.
- Authentication: Allows authentication via identity providers such as Google, Apple, etc.
- Cloud Functions: Hosts the backend logic. Examples of what is done in those functions: crop and resize images, transfer images from one bucket to another, etc.
- Heroku
- Hasura: GraphQL API over postgreSQL, event system (enabling the [3factor app] architecture), etc.
- PostgreSQL.
- Google Cloud
- Storage: Host workout programs' assets for the public.
- App Engine: Host Next.js app.
In reality, things are a bit more complex than they are in the schema:
- There are several instances of Hasura and postgreSQL.
- There are two different Firebase projects: one for the coaches and one for clients.
- Everything is duplicated between staging and production environments.
A few examples
To give you an idea of how everything is put together, I want to show some practical examples of how every piece communicates with one another in some user use cases.
What happens when logging in?
Below is an overview of how the login process takes place. This logic is the same across the different parts of the platform.
- Using the Firebase SDK, sends a request to the Google Authentication service.
- In the client, an observer is listening to all Firebase auth state changes. When the previous request succeeds, the observer gets information about the user such as their unique User ID and a JWT token. The User ID allows to uniquely identify in the whole platform while the JWT token is used to confirm the identity of the user making requests.
- When the user is identified successfully, the client sends a request to the backend (in this case, Hasura) to notify that the user has actually logged in.
- The Hasura GraphQL Engine automatically checks the validity of the JWT token.
- Communicates with the Authentication Service if JWK has expired to get a new one.
- When the JWT token is valid, the database is updated.
- The backend replies with a successful response.
How are coaches' assets uploaded?
Below is an overview of how images are uploaded in DoNotSkip Coaches.
- Using the Firebase SDK, an upload task is started client-side.
- The file is uploaded successfully, the client receives a successful response.
- Metadata about the file is added to the SQL database via Hasura. The JWT token is sent automatically with every request.
- The Hasura GraphQL Engine automatically checks the validity of the JWT token.
- If the JWK has expired, get a new one.
- The token is valid, Hasura proceeds with the update.
- Sends a successful response to the client.
How are workout programs published?
Below is an overview of how program publications work inside DoNotSkip Coaches. The architecture used here is called 3factor app.
- A GraphQL mutation is sent to Hasura. This GraphQL mutation inserts a row in a table called Publish Program.
- The Hasura GraphQL Engine automatically checks the validity of the JWT token.
- If the JWK has expired, get a new one.
- The token is valid and a row is added to Publish Program.
- A response is sent to the client saying that the row has been added successfully.
- Because the response is successful, the client creates a GraphQL subscription to get real-time updates on the publication of the program (using WebSocket).
- An event trigger is set up in the database so that whenever a row is added in Publish Program, a request is sent to a custom webhook (in this case, it's a cloud function hosted on Firebase).
- The cloud function does everything it needs to do in order to make the program available publicly (compresses images, sanitizes text, etc.). Once everything is done, it updates the SQL database to notify that the program has been successfully uploaded. The client is in-turn notified.
The publication process itself is explained in more details below.
- The webhook "Publish Program" is called. A cloud function is started. This function has two purposes: 1) the creation of the landing page for the program and 2) the creation of the program that's going to be used in the app.
- All information regarding the program to publish is retrieved in Hasura.
- Various stuff is done in the function:
- Images are cropped and optimized (if required).
- Pieces of text are sanitized.
- Images are moved in a public bucket.
- Various JSON documents are created.
- The JSON documents are published to an Hasura instance hosting the published programs.
Final words
As you can see, Hasura plays a crucial role in this architecture (3factor app): most of the time, whenever the frontend app wants to perform a backend action that requires authorization, it does so by inserting a new row in the SQL database via the GraphQL API and then subscribes to some database row changes. The cloud functions are invoked behind the scene via a clever event system provided by Hasura.
This cloud architecture would be able to accept a high volume of traffic:
- It's going to be a long time before Hasura becomes a bottleneck (see Scaling to 1 million active GraphQL subscriptions with Hasura). Furthermore, I have set up three different Hasura instances that can scale vertically independently of each other (one for Coaches, one for Publications and another one for User savings). It wouldn't be difficult to put a load-balancer in front of the Hasura instances in order to scale horizontally.
- Cloud functions automatically scale.
I could go on and on and explain other use cases, but I think you get the gist of DoNotSkip's cloud architecture. In the next blog post, I'd like to talk about the code architecture of DoNotSkip App, the mobile app of the platform built with React Native.