In my career as a software developer and architect, I’ve worked with startups and enterprises, small businesses, and government agencies. I’ve launched industry-changing software. And apps that were never used. I’ve worked on fairly stagnant applications and rapidly growing ones. I’ve been through huge data migrations and breaking up monoliths. Concerning software systems and their architectures, I’ve seen a lot.
I’ve been surprised to observe enterprises trying to operate like startups. They try to adopt the mantra, “move fast and break things,” but mostly succeed in moving too fast for their users and breaking their employees’ trust in leadership. They put prototypes into production and wonder why it wasn’t stable or ready to build upon. They spend two years and millions of dollars cranking out siloed products that can’t easily integrate with other products and don’t scale well.
I’ve also been surprised to see startups flail in their attempts to evolve their software. They use borrowed money to buy cheap programmers and tech debt. They reactively change stacks to the latest technologies. They struggle to find solutions to scale with their businesses.
I don’t fault the business leaders of startups and enterprises. I think these problems are due to the immaturity of the software industry. We lack a simple framework for scaling applications and architecture. Strategies for navigating the tension between intentional architecture and emergent design. A process-oriented guide to evolving software architecture.
Quality architecture considers the system as a whole, not just the applications or products within it. Building software applications without architectural considerations is like adding a foundation to a house after it’s been built.
When building a house, you’ve always got to start with a foundation. The foundation obviously needs to be poured prior to building upon it, and it serves to provide stable support for the house. A thin foundation can’t support a two-story house.
The frame of a house is also critical. You can’t just build some walls, toss a roof on top, and call it good. Unless you’re okay with the risk of a snowstorm collapsing the roof or sheer winds toppling your home.
In addition to a foundation and framing, a house would need utilities like power, water, sewer, etc. Electrical, plumbing, and heating/cooling systems are often highly integrated into the structure of the house, and changing the location of a wall or adding a new room is often not trivial if you’re expecting to tap into an existing system.
In the context of building a house, an architect must consider the foundation, framing, and roofing, as well as utility systems in designing a house. Each system of the house supports the functional usage of the house. Everyday tasks as simple as cooking and using the restroom are supported by substantial, well-designed infrastructure.
This brief exploration of house construction is meant to reinforce key principles in software architecture: building stable, well-structured architecture is critical to scaling software. It is also nontrivial.
We live in an era of software development that is highly focused on Agile principles and concepts like rapid prototyping and iterative development. But these principles are casually and liberally applied. There’s nothing “rapid” about building a server or cluster from scratch unless you’ve done it several times or have it scripted. And it’s quite the challenge to “iterate” on SQL databases or services when business objects fundamentally change.
It is trivial to change furnishings in a house, such as upgrading a couch or rearranging furniture. Even painting a wall. However, it is not trivial to move one of the walls. Likewise, it is trivial to change the color of a “Submit Order” button on an eCommerce page or even move that button to a different page. However, it is not trivial to design the order processing engine that hooks up to that button.
There’s obviously a difference between recoloring or relocating a button and handling the action of the button. If you decided to mock the processing of the order — so the workflow can be demonstrated — that would be trivial. Or hard-wire the order processing adapter. But everyone would understand it’s just a Proof of Concept (PoC) or Prototype, as it’s obviously not production-ready. Right?
I recently found out about an application that was written in two weeks by a programmer under a tight timeframe for delivery. He admittedly cut corners to get the app out the door. Two years later, this app is being used in production and was never rewritten. Needless to say, everyone working near it knows it has significant technical debt and is constantly in danger of crashing in production. It’s hard to test and hard to integrate with.
How did key stakeholders miss the fact that this application made for a fine Prototype, but it wasn’t ready to be a stable Product? Why are companies good at launching siloed products but face problems integrating them within a Platform? It’s because we don’t have a common language and understanding regarding key stages of scaling software.
The graphic below shows some key stages software systems often go through on the path to a mature ecosystem.
We start with the Proposal, a well-defined, scoped product and an architectural plan. The product specifies the foundational pieces of the value proposition, critical workflows and business processes, user personas, and so forth. The architectural plan describes the key systems and components that need to be in place to support the building of the product. This doesn’t need to be extensive, but it must contain the primary use cases and key elements for the success of any solution.
With a Proposal in hand, a crude Proof of Concept is in order. It needs to demonstrate the core functionality of the product in a minimal, even non-technical, fashion. Wireframes are easier than mockups. No infrastructure or live environments here, just a simple example of how key functionality would work. The Proof of Concept, when shown to business stakeholders, serves to quickly validate or invalidate the value proposition in the Proposal.
The Prototype takes the software one step further. After the Proof of Concept validates the Proposal, we add in key infrastructure so stakeholders can test it out in a simulated environment. Basic infrastructure, a sample set of data, a brand-aligned experience, and a product that functions just like it will in production. If you’re going to discover a critical problem in your business understanding or technical solutions, this is the place to do so (or in the Proof of Concept stage). After this, it’s going to be much harder to change foundational pieces.
The Pilot serves to get the product in production but releases it only to a group of test users. It has a stable infrastructure and follows the concepts of a Minimum Viable Product (MVP). In this stage, it’s important to collect critical feedback from users to allow for hardening of the product prior to releasing to all users. Plan for bug discovery and fixes, and do a second round of user testing if you can (e.g. an alpha and beta group).
This is the stage many rush to get to, but a quality product will not be the outcome if previous stages are rushed. A stable and fully functional product requires the learnings, adaptations, and hardening of the Prototype and Pilot phases. Once you reach this stage, however, you should have a product that you’re proud of. You’ve onboarded all key user groups and gone through a round of bug fixing after the stable release.
Some businesses only have one software product, but most have a few or even numerous products in operation. Multiple products with siloed infrastructure can sit comfortably beside each other with basic integration between them. This stage focuses on improving existing products and contributing to their care and feeding. It also recognizes the existence of several distinct products and considers whether the future involves stabilization of existing siloed products or scaling to a platform.
Once a company has scaled to support multiple products, it’s often not long before they will need shared infrastructure and/or systems. A core API and/or database, a tasking or event engine, and a messaging or notifications service are all examples of shared systems. Shared infrastructure might mean potentially resource sharing, firewalls, or creating a VPC, to name a few. The key shift in thinking in this stage is that every product, in addition to considering its own needs, becomes part of an ecosystem. A key place of focus and resources is in navigating overall platform needs and product integration.
Here are some useful tips for usage of these stages:
As you read this, I imagine you resonated with some of the principles or examples. Perhaps you’ve found yourself maintaining a Prototype that the business calls a Product. Perhaps you launched your second Product and are being asked to integrate two silos.
If you’ve felt the growing pains of scaling software, I hope this brief articulation of scaling in stages eases that pain. I hope it will give you a framework and some basic language for thoughtfully scaling architecture. If the “P” alliteration isn’t helpful, ditch it. If the architecture metaphor seems problematic, let it go as well. What’s important is the progression, to understand how to wisely and intentionally evolve and scale software.