Choosing building blocks to move faster

Choosing building blocks to move faster

I am currently running a developer survey to help build the best developer experience for WebAssembly on the server. Please consider taking it!

My open source focus for this year is building Atmo, and there is one aspect of the process that I would like to highlight. Since early 2020 I knew roughly what I wanted to build. The specifics of that thing changed over time, but the core idea of a server-side WebAssembly platform was consistent all throughout the year. I didn't write a single line of code for Atmo until late October, even though that was what I wanted to build the entire time. I want to talk about why.

Building blocks

Atmo is built on three core building blocks. When considering what would be needed to provide the kind of fully-featured platform that I was hoping to build, there were a number of core properties I knew it needed to have. I needed it to be extremely simple to deploy. I needed it to have zero external dependencies. I needed it to work in a decentralized environment. During this process is when I solidified the SUFA design pattern that I wanted Atmo to be a framework for. I worked on designing a solution, and ended up deciding that the project would require three core components.

The simplest part, the part that I knew the best, was the web server. I knew I wanted the platform to be centred around building web services like APIs, and I knew that a rock-solid server framework would be needed to ensure it was secure, fast, and easy to use. I had plenty of experience building web servers, so I wasn't too worried about this part.

Core to the idea of a server-side WebAssembly framework (or any function-based system) is the scheduler. The ability to dynamically control and distribute the execution of tiny modules while ensuring they remained coordinated is key to this project. I had several requirements for the scheduler, including modularity, being lightweight, and having tight control over how workers were managed.

The biggest unknown to me at the outset was how to reliably enable distributed computation, and even more so being able to do so in a decentralized manner. The message bus would end up being so essential to the project that choosing the wrong one would have been a huge detriment. I had an ambitious set of needs, and this was the aspect of planning Atmo I struggled with the most. I needed to sink a fair amount of research into the available options, and ultimately decide what was best for the project.

Each of these three components - the server, the scheduler, and the message bus - needed to be in place for this project to succeed, and I knew I needed to plan carefully to ensure they would all work in harmony. The classic choice in the software industry - build or buy - was coming at me from three sides, and I made a choice for each one.

I chose to build these blocks at the expense of moving fast. What I learned along the way is that this decision has its ups and downs - but it was the right one for this project, and I want to explain why. In the end, taking the time to build these components myself allowed me to build something of a higher quality, and actually allowed me to build Atmo itself more quickly than I could have otherwise - in just one weekend.

Dependencies, integration, and requirements

For each of the three components, the decision to build it myself or use something off the shelf came down to one or more of these three things; dependencies, integration, and requirements.

To start, one of the easiest way to disqualify a potential off-the-shelf component is when it introduces dependencies that you can't or won't use. In the case of choosing a message bus for example, most of the available options had a dependency on CGO (the Go language's framework for integrating C-style libraries). This was a dependency I was unable to accept because it makes cross-compilation and distribution of a library harder. I could have accepted this dependency and faced the consequences down the line, but ultimately I decided it was not worth the trade-off for this project.

The ability to integrate the building blocks together was another main factor that came into play. My design for how Atmo would function would mean a tight integration between the three components, and after some research I found that achieving this would be difficult. The message bus and the job scheduler needed to be able to work very closely with one another, and without being able to change the internal workings of the scheduler in specific ways, none of the available solutions were going to work for me.

Another common reason to decide against using an existing component is when it doesn't satisfy your requirements. For this project, I needed to prioritize the performance of the web server, and so I knew I needed an HTTP framework that performed well across a number of benchmarks, but I also needed some unusual capabilities such as being able to swap out the router in real-time. In the end, I decided on a hybrid approach for the web server, choosing to build a custom framework around the well-loved httprouter, which granted the majority of what I needed, and had some extremely well thought-out integration points that made it easy for me to build on top of it. In the case of the message bus, I also had a requirement of decentralization, which the majority of available projects are not.

The result

In the end, a combination of those factors led me to build the three main components of Atmo rather than use something off the shelf. Choosing to build the web server framework caused me to build Vektor. The need for a highly custom scheduler caused me to build Hive. The need for a dependency-free and decentralized message bus with tight integration with Hive caused me to build Grav. Some people may call this yak shaving, but I believe it was fundamental to building Atmo.

For starters, with the massive amount of infrastructure that these projects provided me with, I was able to build the first prototype of Atmo in one weekend. I'm not going to pretend that it was any good, but it worked, and I was able to immediately diagnose every problem I had with it because I knew the constituent components like the back of my hand. Understanding the internals of the various parts made it extremely easy to understand what was happening under the hood, and to gain control that I wouldn't have if I had chosen something pre-made.

Now that work on Atmo has been underway for a number of months, the dividends I am seeing from having built these components myself is staggering. I am able to finely tune the performance of the system, down to the execution of each function invocation, because I have full control over the internals. I am able to build abstractions at each level of the project that make the most sense in the context, which in turn makes each code base individually easier to understand and more useful.

I now have 4 usable and exciting projects available for others to take advantage of, and I am able to help them use each part to its full extent because I understand how they work internally. I don't want the message of this post to be 'you should build all of your project's infrastructure from scratch', because that's not true. For developers building any product, it's important to understand the real value that the build or buy choice means for your users or customers. If the end result to them is absolutely no difference, then why would you waste time building something when you can use something dedicated?

For Atmo and Suborbital as a whole, the goal is to build tools and frameworks to help developers build web services. The tagline is 'Helping you build web services that are powerful, but never complicated', and I believe that building these components meaningfully improves the quality of what I'm providing to them. I think it's important to always make these choices in the context of what you're building, and don't choose one path or another because of what I chose, or because of what you'd chosen for some past project. Make an informed decision each time and be sure to always consider what the end user gets out of it.

If you want to learn more about Suborbital, Atmo, Vektor, Hive, and Grav, head over to the Suborbital website, and sign up for the mailing list for occasional updates. You can reach out to me on Twitter or reach out via the Suborbital discussion forum

Cover photo by Steven Wei from Unsplash

P.S. If you look closely at the Atmo logo, you should be able to see the three components represented!