Contents

Architecting a Scalable Software as a Service

Architecting a Scalable Software as a Service

This post can serve as a reference document that highlights some of the best practices in architecting your Software-as-a-Service (SaaS) web application.

https://cdn-images-1.medium.com/max/800/1*_2y7jPirLNtPHduWzlgphQ.jpeg

Source: Pexels

Why Software as a service?

Software as a service (SaaS) is a flexible type of software distribution model that can be operated by an individual or a 1000~ people organization. The advent of Cloud services has made it possible for anyone to independently run their own Software as a service and build a freemium business on top of it.

It has a relatively simpler system design compared to other types of software services. But without a proper baseline architecture, SaaS can become a gargantuan mess if we are not thoughtful in designing it upfront. I’ve seen SaaS platforms that ended up becoming a huge but fragile monolithic web application with redundant functions lumped within.

The goal of this article is to provide you an architecture that will set up your SaaS for scalability and maintainability. This was written to be used as a high-level design reference.

Hopefully, by the end of this article, you won’t find building SaaS platforms so intimidating!

Table of contents

Requirements of a SaaS platform

Product requirements

  • Access control for users and groups of users.
  • Provide subscription tiers to users and organizations where each subscription type will have access to a set of products.
  • The capability of hosting internal admin tools.
  • Extendable features. We should be able to add a new set of products and features easily.

Design goals

  • Isolated services lead to single responsibility and separation of concerns.
  • Reduce the blast radius of any change e.g. a bug in the analytics dashboard should not affect the admin dashboard.
  • Run web applications in isolation. Each web application serves a group of related features that serve our customers. In large companies, every web application can have its own dedicated team building and running it.

Back to top

The solution: Isolate and reuse

Isolating services into logical components makes our SaaS more scalable. We should aim for our architecture to do the opposite when it scales: the bigger it gets, the more manageable it becomes. Running multiple services is also no longer hard to manage even for a lone garage developer especially now that we have all the Cloud services that are meant to do the job of orchestrating our microservices.

Because we are planning to run these independent feature web apps in our SaaS platform, it makes sense to start with the question “What can we reuse?”.

Isolate

Isolated web applications are formed by grouping related features together to form one web application or group of web applications that represents a product. For specificity, I would call these as Product Web Apps which will be covered more in detail.

For example, all analytics reporting features can be grouped together as an independent web application that will be built and maintained by one dedicated team that has domain knowledge in building analytics reporting products. In large companies, every product web app has its own dedicated team building and running it.

Internal vs public (optional)

Your product web apps can also be grouped into two: internal and public. Having a clear separation between internal and public will allow you to have a dedicated set of secured administration tools routed within your own private network.

Proxying these services internally and publicly is handled by the Routing Service which will be covered in one of the sections below.

Reuse

These are the common functionalities that are shared across the feature web apps with their corresponding services. Each feature web app will need to utilise each of the functionality listed below.

View Code on GitHub Gist

Functionality and service table.

Back to top

The components

These are the logical components that make up our SaaS.

Routing service

Every request needs to be routed to the right page. When a user visits yourwebsite.com/your-path, then /your-path needs to forward the user to its associated requested page or feature web app. The Routing Service ensures that users are getting what they are requesting.

https://cdn-images-1.medium.com/max/800/0*DbSjSr0s4MfN4A-S

In this case, we will need the routing service to call another service to get the path and web application URL mappings together with the required permissions.

These mappings are expected to be updated during runtime. For example, in situations where we need to introduce a new page or change a permission for a user without having to do a release.

The routing service is also responsible for exposing product web apps either internally or publicly. It can be deployed into two separate running instances.

https://cdn-images-1.medium.com/max/800/1*Zs4waI7iescCurl7kPGEyg.png High level design of public and private Routing services setup.

Implementation tip: A couple of possible options are Node.js’shttp-proxy-middlewareor Nginx’sReverse proxy.The latter is idealif you do not need to programmatically retrieve the path, URL and permissions from another service during runtime, more about this in theWeb App Repositorysection.

Back to top

Product web app

Every product web app is exposed through an internally accessible URL e.g.https://my-product-app-1.mydomain.local.Some of these are routed by the Routing Service to be accessible for subscribed users, while some remain internally accessible.

https://cdn-images-1.medium.com/max/800/1*YCk9B5YKZoHdKZxeuA8TMw.png Product web apps routed through the Routing service.

Under the hood, this can be a single or a group of microservices with a web frontend. These web apps are independent from each other, they have separate code repositories, backing services, deployment pipelines, and are ideally owned by different teams. How these are grouped together within the SaaS platform aligns with how the owning teams are structured.

This allows us to break down a large web project into manageable units. Think of this as breaking up a large steak into smaller bites.One product web app contains a group of related features that should not overlap with other web apps.

Implementation tip: Product web apps can be any web application like the usual Express, Node.js and React stack. Using a design system along with reusable micro frontends likeMosaicorOpen Componentshelps keep your products have a consistent look and feel.

Back to top

Web app repository

This service keeps a record of the web applications owned by multiple teams. Routing service calls the Web app repository to get the corresponding Web app URL of the requested Path that matches with one of the entries.

https://cdn-images-1.medium.com/max/800/1*76a5CAllBkZ37Q5hmgUIdw.png Routing service retrieves the Web app URLs and its associated paths.

Here is an example Web app repository table to illustrate its functionality.

View Code on GitHub Gist

Permission information will be provided by theRole based access control (RBAC)service to help us check if the logged-in user has permission to access theproduct web app.

This can be a simple routing and proxy hardcoded hash map if there’s a fairly small number of Product web app entries to maintain.

Implementation tip: This can either be a RESTful service with its own web admin UI or it can be a simple infrastructure as code configuration file within the Routing Service or from an object storage like AWS S3. Implementation will depend on the scale and desired configurability. If you go ahead with the service implementation, you can consider either Java, Go or C# with either a RDBMS or NoSQL datastore. Validation is critical for this service, a misconfiguration of the Path or Web app URL can affect your service’s availability. If you are unsure of the number of products you will support, go with evolutionary prototyping approach. This is the service that you don’t have to build early in the development cycle because it can start as a simple configuration file.

Back to top

Authentication

There should be a dedicated service for authentication that is shared across your services. This is where JSON web token comes in handy because we can easily pass the token and other useful user information across multiple services through the headers.

Authentication can be invoked from the Routing Service or in the Product web app. If Authentication is invoked from the product web app, you are giving the Product web app owners the flexibility to authenticate whichever page they’d like to protect. This flexibility allows the service owners to host publicly available pages like user on-boarding. This approach also lessens the logic executed in the Routing Service.

https://cdn-images-1.medium.com/max/800/1*J4NucQ6TVimFOwKKARFUKg.png Authentication invoked by the Product Web Apps.

Alternatively, authentication can be handled within the Routing Service. This makes the authentication handling easier to audit from a security perspective because there is no need to look into the individual Product web app pages. This also means that there should be some form of whitelisting if there is a need to make a web page publicly accessible. Which is another configurability tradeoff.

https://cdn-images-1.medium.com/max/800/1*1IFMMQuk4cGIf0tnR5CruA.png Authentication invoked by the Routing Service.

Implementation tip: AWS Cognito, Auth0 or any JSON Web Token (JWT) authentication service.

Back to top

Role-based access control (RBAC)

After a user is authenticated, we need to be able to answer questions like the following:

Is the logged-in user allowed to access this page or information that they are trying to access?

Possible answers are:

  • Yes, userAlicebelongs to Organisation A which has permission to access Content A.
  • No, userBobbelongs to Organisation B which has no permission to access Content B.

Is the logged-in user allowed to perform this action?

Possible answers are:

  • Yes, the userAlicehas Admin Role which has permission to remove a registered user.
  • No, the userBobhas Member Role which has read-only permission to all content.

RBAC helps us answer the questions above, hence making it possible for our web apps to permit or revoke users in doing specific actions.

https://cdn-images-1.medium.com/max/800/1*D9ixi8RyMWKFdW-Z2xz4hg.png Product web apps retrieving permissions information from the RBAC service.

Diagram below shows the flow of events that occur when a user is authenticated and has its permissions checked with RBAC.

https://cdn-images-1.medium.com/max/800/0*nySndXqwX-07Lnv_

Pseudo-code below illustrates how RBAC service can be used in specific use cases.

Check if user has permission to view page.

View Code on GitHub Gist

Check if user has permission to perform an action.

View Code on GitHub Gist

Implementation tip: This service is expected to have a relatively higher traffic volume compared to other services, depending on how granular your permissions will get. You can consider similar choices with the Web app repository for its implementation: Java, Go or C#, with an RDBMS datastore and a caching system. A web admin UI will be needed for user permissions management.

Back to top

High level design

Diagram below shows how the components interact during runtime.

https://cdn-images-1.medium.com/max/800/1*JF9f_hSWIJi3CGVZNoFmWg.png

Benefits of this solution include: clear ownership, reuse of common functionalities and separation of concerns.

Back to top

High level sequence

Diagram below shows the sequence of when a user gets routed to the requested page by the routing service. These sequences happen after the Product Web App details are retrieved from the Web App Repository.

Option 1

Invoke Authentication in the Product Web App.

https://cdn-images-1.medium.com/max/800/1*FHBH5zPM0oWjc7ZAw3hWJw.png Authentication is invoked in the Product Web App.

Option 2

Invoke Authentication in the Routing Service.

https://cdn-images-1.medium.com/max/800/1*6iGzW7ttNDz6W6Nf1wLNpQ.png Authentication is invoked in the Routing Service.

Web App Repository, RBAC and Authentication page as a Web App

Web App Repository, RBAC, and Authentication need their own web UI to manage data or receive user input. Web App Repository, RBAC and Authentication all have their own Web Apps. Access to aWeb Appis managed using anotherWeb Appwith a management UI.

https://cdn-images-1.medium.com/max/800/1*IMjdFfACcrlwO4RB772kwQ.png Some Web Apps are used as UIs to other components.

Web App Repository UI allows us to manage Paths and Web App URLs with their required permissions.

RBAC UI is used to manage users, organisations, roles and permissions.

Authentication service’s frontend UI which is the login page is deployed as a Web App.

Back to top

Technical notes

  • Services communicate via HTTPS calls.
  • Services are internal but some of the Web Apps are made publicly available by the Routing Service.
  • Redundancy is not covered in detail here. You can easily add a load balancer in front of the services that make up your SaaS platform and run them in multiple regions.

Back to top

In the long term

In conclusion, the following are some guidelines to revisit to help you successfully run your SaaS platform.

Treat technical documentation as part of the product

Enabling teams that own the Product Web Apps through good documentation whilst taking advantage of the tools that are already available is crucial. Good documentation minimises the need for your teams to go back and forth asking questions about your SaaS platform.

Enable your fellow engineers

On top of the web UI management tools for RBAC and Web App Repository, you can develop a NodeJS / React template that includes the clients to RBAC and Authentication out of the box, to help minimise the work needed to add a Web App to your SaaS Platform. This makes it convenient for your engineers or yourself to create a newWeb Appout of this template without having to spend much effort integrating it to the platform.

Engineering design reviews

Badly written code can cost days to weeks of developer’s time,but bad architectural decisions can cost months to years in comparison. Engineering design reviews are probably more feasible for larger companies; however if you’re a developer on your own, it doesn’t hurt to get feedback from other engineers you know in your community. Spending a reasonable amount of time writing the engineering design document upfront and getting feedback during engineering design reviews will save you more time in the future. Validate software design assumptions and get other engineers to poke as many holes as possible to your intended software architecture (no matter how tempting it might be to take some shortcuts!).

To wrap up, publishing this is article is also a form of design review. Your comments and feedback can help in improving this architecture.

Back to top

I’m hoping to see you build and run your own SaaS very soon!

Check out my other posts about software architecture: