Oct 31st, 2021

🕸 Reducing SaaS Complexity

Building software is hard. Typically, engineering teams use dozens of services for building, deploying, monitoring, and analyzing their product, as well as providers for billing, internal tooling, customer service, and so on.

Once a product is deployed in production, it is continuously refined and expanded with new features, which may require migrations of existing data, phasing out old infrastructure, and bringing in new services when needed.

Most services are set up by hand, even when teams try to use Infrastructure-as-Code tools: Billing configuration like subscription tiers or database migrations and alerts for monitoring are often not included, making it incredibly hard to spin up a new environment when needed, which leads to teams spending a lot of time debugging issues, onboarding new hires, and maintaining their stack.

I imagine that newcomers joining the industry get a bit confused by the arbitrary separation of automation and manual work: Pushing our code so it gets packaged in a container and sent off to some cloud while we apply our migrations manually, or having to clone the backend locally to preview a change.

Maybe your company is different, maybe your engineers can spin up a new environment with all important services in no time, maybe you've figured out internal tooling and debugging, but if you want to get all of that right, your team won't spend its time on building the product, which can cause trouble with management.

So why don't we have all that? In the following, I'll go over a couple of workflows that can make our lives as engineers much easier.

Infrastructure-as-Code, but for everything

Infrastructure-as-Code tools allow defining cloud resources as code. While most major providers like the big public clouds are supported, we could expand the concept to other categories like setting up Stripe, GitHub repositories, database migrations, and all other services that are still configured manually via built-in IaC resources.

By doing this, we can leverage the resource lifecycle and dependency graph IaC tooling uses to determine when to create, update, and delete resources. You could also reference secrets and other resources in your configuration and create additional environments with a single command.

Similar to how we saw the rise of APIs in the 2010s, I'd love to see a move to convert those APIs to IaC resources in a way that all tools can adopt.

Infrastructure-as-Code, but No-Code

If we want everyone on the team to respect our approach to using IaC tooling, we have to make adding new services more accessible to everyone. Simply integrating a new provider shouldn't be rocket science, and it should be as fast as doing it manually.

If we don't invest in accessible IaC, we'll run into manually-configured resources because the entry barrier just was too high, hurting our goal of reproducible infrastructure.

Full-Stack Previews

While platforms like Netlify and Vercel offer previews for every pull request, this usually just covers your frontend (or the backend of your frontend). To test all resources that make up your infrastructure, we'd need a full preview environment for every pull request. This is already possible, and I wrote about it in a recent guide.

Moving more resources into IaC also helps to make preview environments more isolated and complete. The fewer manual steps you need to take to deploy something, the more time you have to build your product.

Observable Infrastructure

For most products, engineering teams deal with a lot of in-house services deployed on public clouds and other external service providers. Mentally visualizing access permissions and the traffic flow between services can become nearly impossible the larger your infrastructure grows, which leads to more time needed to onboard new hires, a lower degree of confidence when rolling out changes, and a higher likelihood of running into invalid access control levels (more than just public S3 buckets).

If we are to continue adding more services to our infrastructure, we must find a way to visualize the dependencies between all cloud resources on different levels, including traffic flows, resource flows (environment variables, secrets, etc.).

Abstracting Commodity Features

We all know the core features every SaaS tool should have, from managing seats and organization structures to permissions and access control, audit logs, single sign-on, user management, and more.

Building all of those features requires a lot of time and effort, which you might want to spend on your product. But getting those enterprise customers might require commodity features, so skipping out on all of them won't do either.

The right abstraction layer could provide a foundation for applications that can be extended to meet more complex use cases.


Thinking about what the future of programming may look like is an open-ended task and it's almost impossible to get right. Throughout the last years, I observed the workflows and patterns engineers building SaaS products for the web followed, and I saw that there's still a lot of complexity that doesn't seem justified.

Solving that complexity requires time that teams might not be willing to spend, resulting in a competitive disadvantage, because whoever can iterate (build, test, debug) faster can adapt better to an ever-changing ecosystem.

With all of that said, I will dedicate the next part of my career to reducing complexity, allowing teams to build with confidence, onboard new hires quicker, and build better products for their customers.

If you're interested in this journey or think I'm missing out on something completely obvious here, I'd love to talk, just drop a mail and we'll be in touch!

Bruno Scheufler

Software Engineering, Management

On other platforms