Prolog …
Stephan (a friend and colleague + my manager during my times at Immobilienscout24, 15 years ago) and I went into a rabbit hole about technical debt. In addition to the two articles [1][2] I recently wrote about it, I also commented at LinkedIn on a post by Matt Watson.
To summarize it, in Matt’s view technical debt is a significant issue impacting company growth that takes weeks to fix and is discussed at board meetings, whereas minor tasks like writing documentation or refactoring code, which can be resolved in a day, are simply maintenance work.
I disagreed by saying that technical debt
[…] can be small and large. Large technical debt can be a sign that it has not been taken care of for too long. I would say the best thing is if technical debt is never discussed outside of engineering. […]
The last sentence caused Stephan to write about Accidental vs. Strategic Technical Debt.
The exception for technical debt, that you should talk about outside of technology is Deliberate or Strategic Technical Debt. Strategic technical debt helps you execute your strategy, and is not accidental, but deliberate—even needed to execute on your strategy. There is no way around taking on that technical debt. One example when to take on such a technical debt is around MVP (First Launch) and before PMF (Product-Market-Fit). Here it is a good thing […]. You take on technical debt, so you can be faster in this crucial phase, not faster to deliver features to customers, but faster to iterate on ideas until you get PMF.
I think, Stephan has an important point here! Before product-market fit technical debt does not count!
But this leads to the question: How to turn a prototype (where technical debt is not an issue) into a production-ready application (where technical debt can be limiting factor)?
Refactor vs. Rewrite
There are two high-level strategies to tackle this situation. A step-by-step refactoring or a large rewrite. Both have their pros and cons. (I touched on this here.) And while I’m overall a strong advocate on refactoring rather than rewrites, this situation - turning a prototype into a production-ready application - requires a detailed evaluation.
Refactorings are a good solution if the technical foundation is solid. But imagine simply stitching different solutions together without caring about non-functional requirements (like maintainability, extensibility, scalability, security, etc.)? In such a situation, local optimizations through refactorings might not be effective because they don’t improve the technical foundation.
But even if a rewrite would be the better long-term solution, reaching such an agreement on a company level is not that easy. The reason for this is simple: After achieving product-market fit, it’s all about scaling the business. If this “only” translates into non-functionality requirements (e.g. scalability to serve more customers) for the tech team, lucky you. But it can also come with additional feature requests resulting in the conflict of goals, what to do first. Extending the prototype and widen the space what needs to be rebuild or start rewriting the application first and sacrifice some short-term business value?
The need for a differentiated strategy
Let’s deep dive on this complex situation and sophisticate solution approach because just using one high-level strategy (refactoring or rewrite) doesn't do justice to it.
Wardley Mapping
I need to admit, I’m a big fan of Simon Wardley and his models to simplify complex real-world scenarios. They are a central piece of my toolbox. One of these is Wardley Mapping which can be particularly useful in this context. It helps to visualize the evolution of components within a system, guiding strategic decisions about where to invest efforts.
Without going to much into details, let me briefly outline the process:
First, identify all system components, from user-facing features to backend services. Map these components along a value chain, showing how each part contributes to meeting user needs. Components directly delivering value are at the top, while foundational elements, like infrastructure, are at the bottom.
Next, assess each component's evolutionary stage: Genesis (novel and unproven), Custom-Built (bespoke but not standardized), Productized (industry-standard solutions), and Commoditized (ubiquitous and utility-like). This helps identify where to focus efforts. For example, custom-built bottlenecks might need productization to improve scalability, while commoditized components may require less investment.
Strangler Fig pattern
Once you have clarity on where to invest first, making use of the Strangler Fig pattern can be a next logical step. The Strangler Fig pattern is a gradual migration strategy where a new system incrementally replaces the functionality of an old system, allowing both to coexist until the old system is completely phased out. It minimizes risk by enabling continuous operation and validation of the new system while progressively deprecating the old one.
Parallel development
Even though the Strangler Fig pattern can be used to revise the technical foundation (which is hard/ impossible with to normal refactorings), there are situations in which even this is not sufficient or where such a setup is too complicated.
In these cases a parallel development can be an option. Parallel development means developing a new system from scratch while the existing legacy system continues to operate and be maintained. This approach involves running two independent development tracks and can be tackled by separate teams if available. One of the main advantages is the ability to leverage new technologies and architectures in the new system without being constrained by the legacy system's limitations. But it comes with very high costs and risks, e.g. the final cutover.
Buy or Build
Wardley Mapping can be also used to make decisions between buying and building components. After visualizing the value chain and assessing the evolutionary stages this overview can be used to evaluate the strategic importance of each component. If a component is critical to your core business and provides a significant competitive advantage, it might be worth building in-house despite higher costs and longer development times. For non-core components that do not differentiate your product, buying a ready-made solution can be more efficient and allow your team to focus on strategic initiatives.
Long story short
There are certainly other approaches to develop a differentiated strategy. The once mentioned above are just examples to underpin, that it’s important to take the time to deeply understand the situation and consider how you can simplify it in order to master the shift.
To sum it up, these would be my input factors for decision-making how to turn a prototype into a production-ready application.
Extent of technical debt: Assess whether the technical debt is so severe that incremental improvements would be insufficient.
Non-functional needs: Determine if the current architecture can master a production use or if a rewrite is necessary to meet common non-functional requirements.
Market position: Evaluate if the market conditions allow for a pause in feature development to focus on technical improvements.
Resource availability: Ensure that you have the necessary resources (time, budget, talent) to undertake a rewrite without compromising business continuity.
What would be your tools and decision factors to master this situation? Write it in the comments!
Manager? Partner :-)