The tech that you need to build your SaaS Product — Part I
You have that idea for a new software product, you’ve done your research, created an audience and promised everyone to solve the problem. In the following I’ll provide you with an oppiniated checklist and best practices for building a SaaS.
Nowadays we have countless tools to build software. Starting from programming languages, frameworks and cloud platforms to nocode app builders. Also the market is full of all kinds of SaaS products driving up user expectations.
Defining the Core
Because there is so much competition, you cannot afford to constantly reinvent the wheel. Rather your main goal should be to get your hands on the core functionality, ASAP.
But what exactly is the core functionality? Let’s say as an example you want to create a new food delivery app. Unless you create a new unique way how users authenticate, you probably don’t want to roll your own user authentication system, right? User authentication seems likely a no brainer, but other subsystems such as order management or delivery tracking might need more thought. Should you build it yourself or just buy a solution?
In the following I am going to go quickly through a set of systems and services which are probably not part of the core, because they are common to many SaaS products and can be reused. Let’s get started.
As already mentioned we definitely should not reinvent the wheel for authentication and just reuse existing serives. Your app should offer at least one authentication provider such as Google or Facebook. You could even decide not to offer email signups so that you don’t have to create different forms for login, signup and password reset yourself.
Sending transactional emails such as order confirmations to your customers is essential. There are plenty of services that offer APIs to send transactional emails at low prices. But you might experience some surprises down the road. As an example, at one occasion a famous email service provider just stopped working for me, because the shared ip address was black listed by a majority of anti-spam services. The support could not do anything and suggested to wait and hope that the shared ip address gets delisted soon. During this time no email could go through, so either I would upgrade and get an expensive dedicated IP address (no thanks) or move to another service. At the end I decided to move quickly to another email service, because the APIs of those services are all quite similar and it required only minor changes in the code. The lesson here is to try to reduce your dependencies on external services as much as possible.
Another point is multi-tenancy. The email service has to support different custom domains, if your customers need to send their emails from their domains. Check the pricing and limitations on custom domains carefully.
There are basically two types of SaaS products in regards of multi-tenancy: B2C and B2B.
For B2B apps it’s better to create a logical partion or database for each of your customers. For one this will reduce the complexity of your code because now you have to worry about one hierarchy layer less. Team hierarchy and permissions management are already complex topics. Moreover you reduce the risk that customers of your customers get mixed up due to some bug which might get you in trouble or even out of business. Also deleting customer data is just a matter of dropping a database rather than search in a huge database for the particular data of this customer and then delete it.
For B2C apps it’s probably easier to go with a single logical database. Especially if you want to create an app with social media character or something like a dating app where customers interact with each other, then you might profit from customer data being closer together. But if you have little customers intactions and rather many objects, then managing roles and permissions becomes too tedious in single logical database.
Role based authorization is commonly used to define permissions and team hierarchy. Often roles are directly attached to authentication context. I am not big fan of this approach, because you need to be in full control of the authentication service in order to set the roles. It’s better to store authorization rules directly in the customer databases where you can control it. This yield a nice separation of concerns, too.
Payments have to be completely outsourced. If you have many different products and subscription plans, it’s better to create invoices on your side and use the providers as pure payment processors. This will reduce the complexity of integrating all of your products with the system of your payment processor because the invoice is the only interface to the external system. This also allows you to add other payment processors in the future for example a POS terminal or switch the payment processor for example due to lower fees. Once again too much of external dependency could slow you down.
Hosting Backend APIs
There are many options to host the backend API. Ranging from bare metal machines to managed app services. I believe as a SaaS company you will not win a prize for building the most fanciest kubernetes infrastructure. The optimal infrastructure should be cost effective, easy to replace and easy to scale. This can be achieved with serverless technology such as Google Cloud Run. Just deploy your docker container and you are done. One downside is that there most likely will be a “warmup" time of several seconds on the first request. But this issue dissappears completly as soon as your traffic goes up. Google Cloud Run is so far the only service that I found that actually charges by request time and not instance time. Checkout this illustration on how request time is charged. This is a huge cost saver.
I used to be a big fan of SQL Databases until I realised that RDBMS are just app frameworks of the past. You know, the ancient greeks would write their code in stored procedures close to the data. Later on they would dump frontend code into tables and generate views from that. At one point object oriented languages became very popular and somehow we ended up forcing objects into tables and calling this pain: “impedence mismatch".
Thankfully we don’t have to deal with this anymore (unless we really have to, because our CTO is forcing us). NoSql document-oriented databases such as MongoDB or RavenDB are on the rise, they perform, they are easy to use and we can directly work with objects and don’t worry about ORM.
The treatment of data as dump objects is very beneficial to our overall design. We tend to focus more on modelling the behavior of our system. The data model becomes the result of the behaviour. The argument that document databases always have to have some de-normalised data is outdated. Today we can create our highly normalised relational model and easily perform joins on documents at database level. Document-oriented databases are very beneficial for productivity and allow us to build the core of our app faster.
Hosting the Database
Unlike stateless backend APIs your database needs persistent storage. Many database providers offer cloud hosting of their database engines. The services also include backup management and maintenance.
The pricings are quite high, though. There are free tier plans that we can use as a starting point instead, but they tend to have quite limited resources.
An alternative is to rent a small vm and self-host a community licensed instance, which could provide enough power for the first few hundreds of users or so. I wouldn’t recommend the big cloud providers for renting vms, they are just to expensive in this area. Self-hosting certainly requires more setup work, but could take us to the point where we make enough profits to switch to a serverless database solution.
There are types of workloads that we want to process asyncronously in the background:
- data processing tasks where the results are not required immediately and that can be put in the background.
- handling external events such as payment status updates from our payment service provider or updates from other integrated systems
- handling internal events
Serverless functions in combination with a messaging service bus offer a good solution for data processing and handling of internal events. External events on the other hand are mostly webhook calls that trigger a http endpoint in our system. For that case it’s better to spin up a Google Cloud Run instance that will handle the incoming webhook calls in the background. Azure, Aws and GCP offer good solutions for a message bus and serverless functions. At the time of writing I am building a more uniform solution based on GCP, so stay tuned!
Wrapping up of Part I
Before this post gets too long, let’s summarise what we learned so far in a simple checklist:
- identify the core business idea of your app
- understand whether the type of your app is B2B, B2C or both
- add authentication providers
- find an appropriate email service provider for your transactional emails
- integrate an online payment provider using a invoices as data interface
- use serverless technology to serve your stateless backend API
- use a document-oriented database such as RavenDB or MongoDB
- host you database on a small vm or select a fee plan when you are just starting, switch to cloud based hosting later
- at the cloud provider of your choice: create a message bus and attach serverless functions to handle internal events
In the second part, I am going to write about UI frameworks, code design, security, DevOps and other SaaS related topics. In the meantime checkout and join our new Meetup group here and share your thoughts with the community.