As someone makes more money, expenses once considered luxuries can suddenly become seen as necessities: It’s called lifestyle creep. In the world of software development, we can suffer from a similar affliction: stack creep.
Thankfully, in the world of open source, developers have the freedom to customize their stacks to combat complexity. Just as organizing guru Marie Kondo urges her audience to mindfully consider whether each of their possessions “spark joy” before deciding to keep or discard them, we too can carefully consider the choices that make up our tech stack. Starting our exploration at the level of the individual developer choosing their stack, we can see how the open source ecosystem offers projects covering the spectrum from minimal to maximal. While open source does not necessarily imply or encourage minimalism, having access to minimalist frameworks to choose from does enable developers to right-size their stack. And when they can’t find a project that fits their needs, open source enables them to create one themselves.
Foundations of minimalism in software development
Minimalism and maximalism in software development have been in a tug of war since the very beginning. Just as Wirth’s Law asserts, the moment hardware became more powerful, extraneous features and complexity found their way into codebases. Theories on why minimalism remained an appropriate approach were quick to follow.
In 1978, when RAM was still counted in kilobytes, the Unix Philosophy laid out a few tenets for software development that were later summarized as “Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.” You’ll see these ideas appear again and again as the Unix Philosophy serves as a basis for many thoughts on simple yet robust software design and development.
A decade later in 1989, Richard Gabriel, a computer scientist known for his work on the Lisp programming language, wrote an article about the language with a section titled The Rise of ‘Worse is Better,’ which argues that added functionality does not necessarily improve software quality. In fact, Gabriel wrote, “The lesson to be learned from this is that it is often undesirable to go for the right thing first. It is better to get half of the right thing available so that it spreads like a virus. Once people are hooked on it, take the time to improve it to 90% of the right thing.” In other words, a “worse” piece of software (with fewer features) is “better” (as a starting point) than software that tries to offer all features out of the gate. And as if to illustrate the tenuous relationship between a minimalist and maximalist approach, Gabriel wrote numerous follow-ups arguing both for and against his original premise.
Fast forward to 2011 when Rich Hickey, creator of the Clojure programming language, gave a talk titled Simple made easy where he argued that “we need to build simple systems if we want to build good systems.” In the talk, Hickey explains that we often conflate simple with easy, just as we do difficult with complex. “Oh, look; I only had to type 16 characters. Wow! That's great. No semicolons or things like that,” says Hickey. “This whole notion of sort of programmer convenience, again, we are infatuated with it, not to our benefit.” In Hickey’s view, choosing simplicity in software is key to our ability to understand it, and our understanding enables us to easily change and debug that software over time. Choosing ease up front while ignoring complexity, he says, will give us initial speed, but will slow us down over the long-term. The key, then, is to build software using pieces that are simple, not necessarily easy.
Lastly, in discussing the idea of minimalism in computing and software development, the Pareto Principle rears its head in various incarnations in software development. Commonly referred to as the 80/20 rule, the Pareto Principle says that 20% of causes are responsible for 80% of results. In terms of software, this can be thought of as 20% of features are responsible for 80% of the functionality, that 80% of users only use 20% of features, and even that 20% of bugs are responsible for 80% of errors. The implication here is that we can achieve 80% of our goal with just 20% of the code. This seems like an obviously important principle to keep in mind when producing minimalist software and choosing the elements of your tech stack.
It all starts with a choice…
Our exploration of minimalism in software development and the tech stack can start by looking from the perspective of individual developers creating new projects.
“The term ‘minimum viable product’ is mostly used in the context of user requirements but it can be applied to engineering as well: Limit the things you bring into your product to what would actually benefit you,” says Tanone, who points to developers’ tendency to import libraries and add features without fully considering their implications. “Minimalism is about recognizing what you really need and then being mindful about getting rid of the things you don't. Obviously, you need to make sure you don't shoot yourself in the foot and build something that is truly unscalable, but at the end of the day the danger of that is usually lower than the risk of over-building.”
“To me, minimalism in software development is not only a desirable design goal, but a survival strategy. I’ve seen too many projects fail, many I’ve been involved in myself, due to an explosion of features and a complex technical foundation that stands on shaky grounds, but was just too tempting to begin with,” says Aufreiter. “More than ever before it is tempting to go the quick and easy way. There’s too many shiny things out there that promise to build your product at the snap of a finger. There’s front-end and back-end frameworks of all kinds, and APIs for almost every possible problem.”
Both hint at the balancing act developers face when choosing their stack: The need to weigh future considerations with immediate needs. Tanone says that one method is to set yourself up now so that you can introduce what you might need later.
“For example, in the context of microservices and Kubernetes, instead of onboarding your services straight to Kubernetes, you could start by containerizing the application with Docker and see how it goes,” says Tanone. “Down the line, when you need to scale, maybe then you explore Kubernetes.”
Aufreiter points to open source as providing the flexibility needed to later make these changes. “What’s important to me is that I build on top of reliable open source technology that does not lock me in. The fewer software components I need to include, the less fragile my system will become,” he says. While Aufreiter may avoid abstractions and frameworks for much of his software stack, they play an important role on the back end. This difference illustrates that a minimalist approach can involve trade-offs regarding where you’re willing to accept the complexity needed to solve your problems. This depends in part on what skills you bring to a project and which it would be best to outsource.
"While I’m a big proponent of being close to the metal in code, the reality looks different to me when it comes to hosting infrastructure,” says Aufreiter, who instead uses managed cloud infrastructure platforms.
Sometimes, lean and minimalist tools and abstractions already exist. Other times, all available solutions to your problem involve an unwanted level of complexity, and then you’re left with another choice: to build something yourself.
Configuration curation as a minimalist alternative
Two realms where we’ve recently seen myriad minimalist alternatives appear are front-end web development and back-end cloud-native infrastructure. Both Kubernetes and front-end libraries like React have been recent targets of developers looking for simpler solutions to their problems. And both realms offer examples for how open source further enables minimalism.
When we work from the foundation of open source software, we have visibility and access where we would not with proprietary software. We can choose what parts we want and discard what we don’t. We see this happen here in two primary ways: We build alternative implementations of other open source technology, even borrowing parts of the code we’re trying to simplify, or we abstract away the complexity that may lie not in the software itself, but rather in its configuration, through packaging and curation.
While K3s and k0s might be one step further down the stack than the managed back ends Aufreiter uses, they reduce complexity through curation and packaging. Using Rich Hickey’s definition, one might argue that Kubernetes is actually simple, though many point to their difficulties in setting up and operating it as evidence of its complexity.
“What makes using Kubernetes quite difficult is that there are so many different infrastructure providers and storage providers and components you need to connect,” explains Miska Kaipiainen, VP of engineering at Mirantis, the company behind k0s. “It's not so much Kubernetes itself. It's this entire stack that runs on top of Kubernetes, basically, that creates the complexity.”
Projects like K3s and k0s remove that complexity by providing developers with an opinionated, yet minimal, abstraction. Both projects do this not only by making all the choices for you, but by packaging it all up as a single binary, dependencies included; when updates are needed, you simply replace the executable with the new one.
Kaipiainen says that there are plenty of great Kubernetes distributions for people who specialize in running Kubernetes, but they wanted to make something for developers instead.
“Some bigger Kubernetes distros include all kinds of accessories bundled into the package,” says Kaipiainen. “k0s is a stripped down, bare bones Kubernetes distribution. We let our users install everything they want on top, but nothing extra comes included.”
And while k0s was created for developers, Kaipiainen points out that it is production-grade and could actually serve as the basis for the managed back-end tools that Aufreiter relies on.
K3s creator Darren Shepherd highlights an interesting point: Abstraction and batteries-included approaches can offer the simplest answer to a problem, without sacrificing functionality or introducing unnecessary complexity.
K3s is a lightweight Kubernetes distribution that focuses on making it simple to spin up a small, three-node, production-grade Kubernetes cluster for developers. “It’s an extremely opinionated package of Kubernetes that focuses on a very simple user experience to get it up and running. It doesn't remove any functionality, it just makes a lot of choices for you,” says Shepherd, who first created the project in 2018. “The vast majority of what I do is try to figure out how to package technology in a more usable way for end users. I take best practices and what we've learned over the years and then do it all for you, so you don't have to figure it out.”
When in doubt, build it out
The early 2010s saw the rise of frameworks and libraries such as AngularJS, React, and Vue, which made it easier to make browser-side user-interface components. Over time, however, these tools became larger and more complex as they tackled more difficult problems. In recent years, alternatives have appeared to help build these interactive components with as little code and complexity as possible.
Meanwhile, projects like Alpine.js, htmx, and Preact each take a slightly different approach, there are commonalities. For example, all are extensible, which means that the core functionality can remain laser-focused, while additional features can find a home in add-ons and integrations. They are all open source and are built with and interact with open source software and standards. And they all provide some extended functionality on top of other open source software or standards without obstructing access for other technologies, which means that they are interchangeable as well as compatible with other software.
These projects follow many of the principles outlined in minimalist thinking in computing: They do one thing well and work together (the Unix Philosophy), they enable developers to reach a “worse” product quickly (“Worse is better”), without introducing complexity, and they operate without becoming intertwined with other technology (“Simple made easy”).
Alpine.js creator Caleb Porzio says that Alpine.js is essentially a pared down version of Vue that he built from scratch, that “came out of this belief that you should be able to make a drop-down, a modal, and tabs, easily.”
While React and Vue create their own “virtual” document object model (DOM) to replace the browser’s DOM, Alpine.js forgoes that option and simply interacts with the browser’s native DOM. Porzio built Alpine.js to be interchangeable and compatible with other frameworks, including Vue itself, and with its latest release, Porzio switched to using Vue’s reactivity engine, which means users can easily swap between the two frameworks.
On the other hand, Preact, a three kilobyte alternative to React, uses “the thinnest possible Virtual DOM abstraction on top of the DOM” and provides an API that is “largely compatible” with the React API.
While Preact creator Jason Miller says that the framework’s small size helps offer a justification to limit extraneous functionality, he says it isn’t the main goal. “Preact isn't necessarily trying to be the world's smallest framework,” he says. “It's trying to be the readable, understandable, minimally-indirected substrate on which you can build all of your specific stuff for ever-changing product requirements.”
Miller’s emphasis on understandability echoes Hickey’s point in Simple made easy that understanding is key to creating reliable software, another feature Miller notes. “You can't really change the foundation of a house after you pour it and let it set, just like it's hard to change that lowest level of framework when you pick it,” he says. “The original point with Preact was to demonstrate that you don't need a huge base on which to start, you just need a solid base.”
The ability to easily troubleshoot is an added bonus of an easily-understood codebase, a characteristic that Miller points to as a way to differentiate between complex and simple systems.
Can minimalist open source move standards forward?
All of these projects try to provide as much functionality as they can with as little code and complexity as possible by staying close to the layer they augment. Miller is explicit, however, that this is partly because he would like to see this functionality become offered as part of a standard, and not just for the sake of minimalism and interoperability.
“The goal for Preact is to very slowly disappear, shift behind the scenes, and focus on an ever shrinking little area of value—as the DOM gets better, as standards change, and as developer behaviors change—to the point where it's sort of like a little polyfill for some behaviors that are decidedly not going to get added to the web, like diffing,” says Miller. “I think that would be a totally reasonable end result for Preact.”
While Gross isn’t holding out hope, he agrees that “if HTML just gave you this functionality out of the box, we could build basic web apps with a lot more interesting functionality in them as standard HTML-based web apps. It would open things up quite a bit.”