At some point, almost every growing SaaS product becomes more difficult to maintain.
Features that used to take a few days now take weeks. Small changes break unrelated parts of the application. Deployments become stressful, and the engineering team spends more time fixing issues than improving the product.
Eventually, someone suggests rebuilding everything.
A new framework. A cleaner architecture. Perhaps microservices instead of a monolith.
It sounds appealing. Starting again means getting rid of all those decisions we would make differently today.
But here’s something I’ve learned from building and maintaining software over the years:
A difficult codebase doesn’t necessarily need a complete rewrite. Sometimes, what it really needs is a better understanding of its architecture, clearer boundaries and a few carefully chosen improvements.
So how do we know the difference?
I want to share a practical method you can use with your own SaaS product, without having to start an expensive transformation project.
1. Start with the problem, not the technology
Before looking at frameworks, infrastructure or architectural patterns, I’d ask one question:
What exactly is preventing the product from doing what the business needs?
This sounds obvious, but it’s extremely important.
Sometimes the problem isn’t the architecture itself.
It could be a slow database query, an unreliable third-party integration, insufficient test coverage, a complicated deployment process or too many responsibilities concentrated in one part of the application.
These are different problems. They don’t necessarily need the same solution.
Here’s how I would start.
Take the three most important engineering problems currently affecting your product.
For each one, write down:
Don’t start by deciding that you need microservices.
Start by identifying what needs to improve, how it affects the business, and how you’ll know whether you’ve fixed it.
If you can’t describe the problem clearly, you’re probably not ready to decide on a rewrite.
2. Measure how expensive change has become
One of the clearest signs of architectural problems isn’t necessarily slow software.
It’s software that becomes increasingly difficult to change.
A small feature might require modifying several unrelated modules. A database migration might affect multiple workflows. Engineers become afraid of touching certain parts of the codebase because they don’t fully understand the consequences.
This is where I’d introduce something useful: a change-cost map.
Instead of asking whether the codebase is good or bad, examine the last five meaningful changes your team delivered.
Record:
This is an illustrative example, not measured data from a particular company.
Now look for patterns.
Are seemingly unrelated parts of the system changing together?
Are engineers repeatedly modifying the same files for different business requirements?
Does every new feature require another workaround?
Are failures happening because responsibilities aren’t clearly separated?
That’s much more useful evidence than simply looking at how old the codebase is.
A ten-year-old application with clear boundaries can be easier to maintain than a relatively new product with poorly defined responsibilities.
And it’s important to distinguish architectural complexity from other issues. Slow delivery can also come from unclear requirements, overloaded teams, review delays or missing product decisions.
We shouldn’t blame the architecture without evidence.
3. Find the change that keeps causing other changes
This is one of my favourite ways to identify a real architectural problem.
Imagine that every time you change a subscription rule, you also need to modify billing, notifications, user permissions and reporting.
That doesn’t automatically mean your entire application needs replacing.
It may mean those parts of the system depend too heavily on the same implementation details.
I’d investigate three things.
Responsibilities: Does each module have one clear reason to change, or is business logic spread across multiple places?
Dependencies: Can a component be changed and tested without understanding half the application?
Data ownership: Are different parts of the system directly modifying the same tables and making assumptions about each other’s data?
The objective isn’t to eliminate every dependency. Software components need to work together.
It’s to make dependencies intentional and understandable.
Sometimes the most valuable architectural improvement is simply introducing a clearer interface between two parts of the application.
You may not need a new service, a new database or another framework.
You might just need to move business rules into the right place and stop other modules from depending on their internal implementation.
4. Run three practical checks before deciding
Here’s a small architecture review you can perform without reviewing every line of code.
Check A: Can one important feature change independently?
Choose a business rule that your team expects to modify soon.
Trace the full change from the user interface through the API, business logic, database and any background jobs.
Record which components would need changing.
Is the responsibility clearly owned?
Are there direct database dependencies across unrelated modules?
Can we test this change without testing the entire platform?
Would another engineer understand where to make the change?
If one simple change affects several unrelated areas, you’ve found a possible architectural constraint worth investigating.
Check B: What happens when a dependency fails?
Choose one important third-party integration.
Then review what happens when it times out, becomes unavailable or returns an unexpected response.
Check whether the application uses appropriate timeouts, bounded retries, failure handling and monitoring.
Also consider what happens when the same operation is retried.
Could a customer be charged twice? Could an order be created twice? Could the system send duplicate notifications?
Test safely in a controlled environment rather than intentionally disrupting production.
If the integration is fragile, fixing that boundary may deliver more value than changing the overall architecture.
Check C: Can we deploy and recover safely?
Look at a recent deployment.
How long did it take to reach production?
What testing was required?
If the release caused a problem, could the team roll back the change safely?
Were database migrations backwards-compatible?
Did the team know which signals to monitor after deployment?
When releases are difficult, the root cause may involve architecture, deployment practices, testing or several of them together.
A better deployment process can sometimes improve delivery speed considerably without changing the underlying architecture.
5. Use this decision framework
Once you’ve collected evidence, compare the options.
Notice that there are actually four possibilities.
A complete rewrite isn’t the only alternative to refactoring.
Sometimes replacing one problematic component is the most sensible option.
That’s why I prefer thinking in terms of the smallest architectural boundary that needs to change.
There are genuine reasons to rewrite a platform, especially when the technology is no longer supportable, critical requirements cannot be met safely, or the cost of incremental changes becomes unjustifiable.
But those reasons should be demonstrated, not assumed.
6. A practical example: an unreliable order-processing workflow
Let’s imagine a SaaS platform where incoming orders occasionally fail.
The team initially believes the application needs to move from a monolith to microservices.
Before doing that, I’d trace the complete order journey.
An order arrives through an API. The application validates it, writes to the database, calls an external provider and updates the order status.
During investigation, we discover that the external provider sometimes takes too long to respond.
The entire request waits for that response. When it fails, retries can repeat operations that have already succeeded.
That’s an important problem, but it doesn’t automatically justify splitting the whole application into microservices.
A more focused improvement might be:
Define the order’s possible states and which transitions are valid.
Separate order acceptance from external processing where appropriate.
Introduce a durable queue or job mechanism if the workflow requires one.
Apply bounded retries and appropriate timeouts.
Make repeated operations safe through idempotency.
Add monitoring for failures, processing delays and stuck jobs.
Test the failure and recovery paths before deployment.
Some systems won’t need a queue. Others may already have one.
The right solution depends on the actual workflow, consistency requirements and operational constraints.
But the important lesson remains:
We can sometimes solve a significant reliability problem by improving one workflow rather than changing the architecture of the entire platform.
7. What if the architecture genuinely needs replacing?
Even when a component needs rebuilding, I would rarely choose to replace everything at once without examining the alternatives.
A safer approach is often to migrate functionality progressively.
For example, suppose your application has a large reporting module that is tightly coupled to transactional workflows.
You could start by defining a stable reporting interface, then move one carefully selected reporting capability behind it.
The existing application continues serving the other workflows while the replacement is tested.
Over time, you can migrate more functionality and remove the obsolete implementation.
This is related to the Strangler Fig approach to application modernisation, where existing and new functionality coexist during a gradual migration. The AWS guidance on this pattern provides another useful reference.
It doesn’t mean every monolith should become microservices. Sometimes the right destination is still a better-structured monolith.
The principle I value is simpler:
Reduce migration risk by changing one well-defined boundary at a time, and remove the old path once the new one has been verified.
8. A reusable AI-assisted architecture review
AI can help make this investigation faster, particularly when there’s a large codebase or incomplete documentation.
But I wouldn’t ask an AI assistant something as broad as:
“Review my project and tell me if I should rebuild it.”
That invites an answer based on incomplete context.
Instead, give it a specific task, require evidence and make it separate observations from assumptions.
Here’s a prompt you can adapt for Claude Code, Codex or another coding assistant with access to your repository.
Architecture review prompt
Copy into your coding assistant
Act as a senior software engineer reviewing an
existing SaaS application.
Your objective is NOT to recommend a rewrite.
First, understand the architecture and identify
the smallest set of changes that could address
the problems described below.
Business problem:
[Describe the issue and its impact.]
Affected workflow:
[Describe the user journey or business operation.]
Known evidence:
[Include relevant metrics, incidents and logs.]
Repository scope:
[Specify the files, directories or modules to inspect.]
Review the system and produce:
1. A map of the affected workflow, including
its entry points, dependencies, data access,
background jobs and external integrations.
2. A list of architectural constraints that
are directly supported by repository evidence.
Reference exact files and symbols.
3. Any missing information that prevents a
reliable conclusion.
4. Three possible approaches:
- Stabilise the existing implementation
- Refactor the affected architectural boundary
- Replace the component where justified
5. For each approach, explain:
- What would change
- Expected benefits
- Risks and trade-offs
- Testing requirements
- Deployment and rollback considerations
- What must be measured to verify success
6. Recommend the smallest viable improvement
supported by the available evidence.
7. Provide a bounded implementation plan with
acceptance criteria.
Do not modify code.
Do not invent metrics or assume production
behaviour can be inferred from source code.
Separate confirmed observations, hypotheses
and recommendations.
If the evidence is insufficient, explain what
needs to be investigated before making a
technical decision.A few important precautions:
Only give the assistant access to repositories and data you’re authorised to share. Avoid exposing credentials, personal information, confidential customer data or sensitive production logs.
And most importantly, verify its findings yourself.
An AI tool may identify a dependency in the repository, but it cannot automatically know how often that dependency fails in production, what a customer experiences or which business trade-offs matter most.
The output should help an engineer investigate the problem, not replace the investigation.
9. Turn the findings into one engineering decision
After the review, I’d create a short decision record.
Not a 40-page report. Something the team can actually use.
Engineering decision record
Problem: What is preventing the product from moving forward?
Business impact: What is this costing in failures, time, risk or missed opportunities?
Evidence: What have we measured or verified?
Options: Stabilise, refactor, replace a component or rebuild.
Decision: Which option are we choosing, and why?
Scope: What specifically will change, and what will remain untouched?
Acceptance criteria: How will we know that the improvement worked?
Rollback: What happens if the change causes unexpected behaviour?
Owner: Who is responsible for implementing, reviewing and maintaining the result?
If this document cannot be completed confidently, the next step is probably further investigation rather than a rewrite.
10. A practical plan for the next ten working days
If you want to apply this to your own SaaS platform, here’s a possible starting plan.
This is a diagnostic and improvement-planning exercise, not a promise that every architectural problem can be solved in ten days.
For complex or sensitive systems, investigation and approval may take longer. That’s perfectly reasonable.
I’d rather make one well-supported decision than rush into a transformation we cannot justify.
11. What building my own software taught me
I’ve been building software for more than 14 years, including my own open-source products and production systems across different industries.
One of the most valuable lessons came from building and maintaining pH7Builder, a long-running open-source platform for online communities.
Over time, I learned how much responsibility comes with creating your own architecture, frameworks and internal abstractions.
What looks elegant when you first build it may require years of maintenance, compatibility work, security updates and documentation.
Today, I’d be much more careful about introducing complexity unless there’s a real need for it.
You can read more about those lessons in What Building an Open-Source Dating Platform Taught Me.
I believe the same principle applies when modernising an existing SaaS product.
The goal isn’t to create the architecture we’d enjoy building from scratch.
It’s to make the existing product more reliable, easier to change and better suited to what its users actually need.
12. What should you do now?
If you’re responsible for an existing SaaS application, I would start with one simple exercise.
Choose the engineering problem that is currently costing your team the most time or causing the greatest difficulty.
Then answer these five questions:
What exactly is the problem?
What evidence supports it?
Where does the problem originate?
What’s the smallest reasonable change that could improve it?
How will we verify that improvement?
You don’t need to understand the entire application before you can start improving it.
But you do need enough context to avoid solving the wrong problem.
And if you don’t have the evidence yet, finding it is already a valuable next step.
Want help investigating or implementing the solution?
I work with product teams and businesses that need experienced engineering judgement and hands-on delivery.
That can mean understanding why an existing system has become difficult to maintain, reviewing an architectural decision, addressing a production reliability issue or implementing a focused improvement.
You can use the methods in this article with your own team.
And if you’d prefer someone to take responsibility for the investigation and implementation, that’s the kind of engineering work I can help with.
Explore how we can work together
Pierre-Henry Soria
Senior Software Engineer & Technical Consultant





