LLM.coPrivate, self-hosted LLM deployments
Legal AI infrastructure for firms
AI RFP discovery and response drafting
Automatic.coBusiness process automation
Secure AI virtual data rooms390+
Projects shipped
Since 2013
0
Rewrites recommended by default
Only when the evidence says so
100%
Decisions documented
As ADRs your next hire can read
2–4 wks
Typical review
System read, findings written
The decisions that matter
Five choices that are expensive to reverse
Most architectural mistakes are not bad code. They are one of these decisions made by accident, early, before anyone realized how much would end up depending on it. Get any one of them wrong and the fix later is rarely a refactor — it's a migration, with real data, real customers and a maintenance window nobody wants to schedule.
The data model
Every table, key and relationship becomes load-bearing the moment real data lives in it, and a schema migration on a production database with customers on it is a different order of problem than one on an empty one. Get the entities and their relationships right early; get the indexing or column types wrong and you can usually still fix those later.
Service boundaries
Where one service ends and another begins decides what can change independently, what has to deploy together, and which team owns which outage. Draw the boundary along a line that genuinely never changes together — around a business capability, not a technology layer — or you end up paying a network hop for what should have stayed a function call.
The tenancy model
Shared schema, one database per tenant, or something in between decides your isolation guarantees, your cost per customer, and how badly one noisy tenant can ruin everyone else's day. Retrofitting stronger isolation onto a shared-schema product after a large customer demands it is one of the more expensive corrections on this list.
Hosting and cloud commitment
AWS, GCP, Azure, or a boring VPS — the platform decides what you get for free, like managed Postgres, queues and secrets, versus what you build yourself. Multi-cloud portability sold as a feature to a company with one product and no regulatory reason for it is a tax paid every day for an option almost nobody ever exercises.
Build versus buy
Auth, billing, search and email are solved problems with mature vendors, and building your own is usually a multi-month distraction from the thing that actually differentiates the product. Buy the commodity, build the part that is genuinely your business, and revisit that line only when volume or cost actually justifies it.
The contract with the outside world
Once another system, or another team, integrates against an API or a shared database, its shape becomes effectively permanent — every consumer that builds on it makes changing it more expensive than getting it right the first time would have been. Version early and mean it, because "we'll fix it later" rarely survives contact with someone else's production dependency.
Monolith vs. microservices
The honest default is a well-structured monolith
This argument gets treated as a matter of taste. It isn't — it's a question of team size, deployment independence and where the load actually lives, and for most companies the answer is not close. The debate is worth having in specifics, not in the abstract, because the right architecture for a five-person team and the right architecture for five engineering teams are not the same design wearing different clothes.
Start with one deployable
A monolith with clean internal boundaries — modules that don't reach into each other's tables — is faster to build, cheaper to operate and easier to reason about than a dozen services for a team that hasn't outgrown one. Most companies that split early are solving an organizational problem, too many engineers stepping on each other in one codebase, with an architectural tool, months before they actually have that many engineers.
When microservices earn their keep
Independent scaling needs, one workload running at ten times the load of the rest, independent deployment for genuinely separate teams, or a hard isolation requirement — these justify the operational cost of network calls, distributed tracing and the failure modes a monolith doesn't have. Absent one of those, the split adds latency and on-call burden without buying anything back.
Event-driven architecture is a tool, not a virtue
Publishing events instead of calling a function buys loose coupling and gives up nothing when there is only one consumer to begin with. It also trades a bug that fails loudly, a broken function call, for one that fails silently, a message published and never consumed, and that trade is only worth making once several independent things genuinely need to react to the same event.
Premature optimization for scale that never arrives
Sharding a database for load you haven't seen, or designing for hundred-region failover with fifty active users, spends months of engineering on a problem you may never have, and makes the schema harder to change while you're still learning what it should be. Build for the load you can demonstrate, and keep the parts that would need to change under real scale cheap to change later.
The database-per-service tax is real and often unpaid
Splitting services without splitting their data just moves the coupling somewhere less visible — two services sharing one database are still one system, whatever the deployment diagram claims. If you split the service, split the data ownership with it, or accept honestly that you have a distributed monolith with extra network hops and less transactional safety.
What it costs
Technology architecture consulting pricing
Real ranges. The variable that moves a quote is not codebase size — it is how quickly we can talk to the engineers who actually run the system and get read access to production, rather than working from a diagram someone drew two years ago. A codebase nobody can explain end to end costs more to review honestly than one with a single, findable owner, no matter how many services it's split into.
| Engagement | Commitment | Timeline | What's included |
|---|---|---|---|
| Architecture review | Fixed scope | 2 – 4 weeks | A read of the existing system — data model, service boundaries, hosting, dependencies — against the load and team size it actually has, written up as specific findings and a prioritized list, not a slide deck of best practices. |
| Greenfield architecture design | Fixed scope | 3 – 6 weeks | The five decisions above made before the first line of code: data model, service boundaries, tenancy, hosting, and build-versus-buy, documented as ADRs a team can build directly against. |
| ADR backfill for an undocumented system | Fixed scope | 3 – 6 weeks | Interview the engineers who remember why, and read the code where nobody does, then turn tribal knowledge into decisions the next hire can actually read instead of re-deriving from the source. |
| Rewrite feasibility assessment | Fixed scope | 3 – 5 weeks | An honest, evidence-backed answer to whether a rewrite is justified or whether the same money spent on incremental change gets further — including the case for saying no, written for whoever has already decided to rewrite. |
| Ongoing architecture advisory | Ongoing retainer | Ongoing | A senior architect reviewing significant decisions before they ship — a new service, a schema change, a vendor contract — rather than after, when reversing them costs ten times as much. |
Ranges assume US-based senior architects and include the written findings and ADRs rather than quoting documentation separately. A review priced well below these bands is usually an opinion, not an audit — check that it includes reading the actual code and talking to the engineers who run it, not just the diagram from two years ago.
Architecture review
What a review of an existing system actually checks
Not what the architecture diagram claims — what the system is actually doing, which is usually a somewhat different document. Most of what a review finds was never written down anywhere, which is exactly why it needs a second set of eyes rather than another internal retrospective.
Where the coupling actually is
Which modules import each other's internals, which tables get written by two different services, and which deploy can't happen without another one going first. The diagram is aspirational; the import graph and the deploy log are not.
What the data model is quietly forcing
A schema decided three years ago under different assumptions constrains everything built on top of it, and a lot of what looks like an application bug is actually a data model paying interest. We look for the tables that get special-cased everywhere they're touched — that's usually where the real model no longer matches the one in the database.
Single points of failure nobody named
The shared database every service depends on, the one engineer who understands the billing sync, the third-party API call sitting synchronously inside a critical request path. None of these show up on an architecture diagram, and all of them show up in an incident review.
Whether the operational cost matches the benefit
Kubernetes for three services and two engineers, or a dozen microservices deployed by a team of five, is a real ongoing tax — on-call complexity, deploy coordination, the cognitive load of tracing one request across six log streams. Sometimes it's justified. Often it was inherited from a blog post rather than from an actual requirement.
Whether the org chart matches the system
Conway's Law is not a joke: a system built by four teams that rarely talk tends to end up with four modules that don't talk cleanly either, regardless of what the architecture diagram says they should do. A mismatch here shows up as constant cross-team coordination for changes that should have been simple.
The rewrite question
When the right advice is "do not rewrite it"
This is the hardest conversation in the practice, because it usually happens after someone has already decided and is looking for a partner to execute, not a second opinion. The output either way should be a decision someone can act on, not a diplomatic summary that avoids picking a side.
Most rewrites are a proxy for a different complaint
"The code is bad" is often really "we don't understand it," "it's slow to change," or "the person who wrote it left." Those are real problems, but a full rewrite fixes them by also throwing away years of edge cases the original code quietly handles — and those come back one support ticket at a time, months after everyone has moved on to the new system.
The Strangler pattern is usually the honest alternative
Route new functionality to new code behind the existing interface, migrate one capability at a time, and keep the old system earning revenue while you go. It's slower to announce and faster to actually ship, and it never puts the whole business on hold waiting for a big-bang cutover that slips.
How to say no to someone who has already decided
Bring evidence, not an opinion: what specifically breaks under the current architecture, what it costs to fix incrementally against what a rewrite costs and how long it realistically takes, and what has historically happened to rewrites of similar scope elsewhere. Sometimes the evidence says rewrite — the job is following it in either direction, not defending a prior position, ours or theirs.
Write the recommendation down either way
Whether the answer is rewrite or don't, put it in writing with the evidence attached, dated, and addressed to whoever has to defend the decision later. An undocumented verbal recommendation gets remembered as whatever turns out to be convenient eighteen months on; a written one is accountable to what it actually said.
How an engagement runs
From reading the system to decisions your team can build against
Week one is spent reading the actual code and talking to the engineers who run it, not reviewing the diagram from the last time someone documented this. The following two to three weeks turn that into specific findings — which decisions are load-bearing, which are cheap to change, and which already need revisiting — written as Architecture Decision Records rather than a slide deck presented once and then forgotten. What you're left with is a document your next hire can read to understand why the system looks the way it does, not only what it looks like. Where the system already has ADRs from a previous review, we start by checking which ones still hold, because an architecture document nobody has revisited in two years is a claim, not a fact.
Questions
Frequently asked questions
What teams ask before a first call.
A review of the decisions that are expensive to reverse — data model, service boundaries, tenancy, hosting commitments, and where you've built something a vendor already sells — against the load and team size the system actually has today. The output is written findings and, where useful, Architecture Decision Records, not a diagram and a deck.
It is not a rubber stamp on whatever a team has already decided to do, and it is not a sales pitch for a rewrite. Where the existing architecture is right for the company's actual size and growth rate, the report says so, and that answer costs the same to produce as the alternative. We'd rather lose the engagement than write a report that just confirms what a team already wanted to hear.
For most teams, a well-structured monolith is the honest default — one deployable, clean internal module boundaries, faster to build and far cheaper to operate than a dozen services maintained by a small team. Microservices earn their operational cost when a specific workload needs to scale independently, when genuinely separate teams need to deploy independently, or when isolation is a hard requirement rather than a preference.
A lot of microservice adoption solves an organizational problem, too many engineers stepping on each other in one codebase, with an architectural tool, months before the team is actually that size. If that's the real problem, a monorepo with clear module ownership solves it more cheaply. And if you can't currently deploy your own monolith without a genuinely complicated release process, that's usually the actual problem, and it long predates the microservices conversation.
An ADR is a short written record of a significant decision — what was decided, what the alternatives were, and why they were rejected — filed next to the code it governs. A diagram shows what the system looks like today; an ADR is the artifact that tells the next engineer why, which is the part that actually stops them from undoing a decision for a reason it was already rejected.
We write them as the deliverable of a review, not an afterthought, because the alternative is the same conversation happening again in eighteen months with a different engineer and no memory of the first one.
With evidence rather than an opinion: what specifically breaks under the current architecture, what an incremental fix costs against what a full rewrite costs and how long it realistically takes, and what tends to happen to rewrites of similar scope. Most "the code is bad" complaints turn out to be "we don't understand it" or "the person who wrote it left," which a rewrite fixes at enormous cost by also discarding years of quietly-handled edge cases.
Where a rewrite genuinely is the right call, we say that too, and usually recommend the Strangler pattern over a big-bang cutover — migrate one capability at a time behind the existing interface so the business keeps running while it happens.
When several independent parts of the system genuinely need to react to the same thing happening, and the coupling of calling each one directly has become a real, measured problem rather than a theoretical one. Below that, publishing an event where a function call would do trades a bug that fails loudly, a broken call, for one that fails silently, a message nobody consumed, and that's a worse trade, not a more sophisticated one.
When it does earn its cost, it needs the same rigor as any other distributed system: idempotent consumers, a way to replay a missed message, and monitoring on whether anyone is actually listening. Event-driven without that operational discipline is just a queue that quietly drops things.
A review of an existing system is the smaller engagement, usually two to four weeks; designing the architecture for something new takes longer. The table above sets out the other common shapes, including backfilling ADRs for a system nobody ever documented.
What moves the number is how much of the current system has to be read before it can be assessed. A well-documented codebase is quick; archaeology is not.