Why Your First Architecture Should Be a Monolith

Why Your First Architecture Should Be a Monolith

Quick answer: Microservices solve organisational problems, not technical ones. Independent deployment, independent scaling, fault isolation and team autonomy only start paying off when several teams need to ship without coordinating with each other. A team of four has no such problem, so it pays every cost of a distributed system — network failures, lost transactions, eventual consistency, harder debugging — and collects none of the benefits. Start with a modular monolith: strict internal boundaries, one deployable, one database, designed so you can split it later when a real boundary appears.

One money transfer as a single transaction inside a monolith, versus the same transfer split across five services with no transaction boundary
One money transfer as a single transaction inside a monolith, versus the same transfer split across five services with no transaction boundary.

Every founder building a first product hears the same advice: start with microservices so you don't have to rewrite later. It sounds like prudence. In practice it is usually the most expensive decision of the first year, and it gets made before anyone knows enough about the product to make it well.

The running example throughout is a cross-border money transfer product, in the spirit of Wise, Revolut or Payoneer: GBP leaves London, EUR arrives in Lisbon. Payments are a good test case because they are unforgiving. They punish architectural mistakes in a currency you can count.

What do microservices actually give you?

Microservices are a real and good pattern, adopted at scale for concrete reasons. There are four:

  • Independent deployment. The payouts team ships on Tuesday without waiting for the onboarding team's release.
  • Independent scaling. FX quoting handles ten times the traffic of compliance screening and gets ten times the hardware, instead of scaling everything to match the busiest component.
  • Fault isolation. The notification service falls over and transfers still complete.
  • Team autonomy. Each team owns its codebase, release cadence and technology choices, and decides without a cross-team meeting.

Read those four again. Every one is a statement about coordination between groups of people. Independent deployment matters when two teams share a release train. Team autonomy matters when there are teams, plural. Even scaling and fault isolation are mostly about who gets paged and who is allowed to change what.

Microservices are an answer to Conway's law: an org chart expressed in network calls. That is not a criticism. It is exactly why they work at companies with dozens of engineering teams.

The same point as a table, because it is the whole argument:

Benefit What it requires before it pays off Team of 4, one product Six teams, one platform
Independent deployment Multiple teams contending for one release You already deploy whenever you want Real, measurable friction removed
Independent scaling Components with genuinely divergent load profiles One app, one autoscaling group, done Meaningful infrastructure savings
Fault isolation Components with different reliability targets A crash is a crash either way Blast radius genuinely contained
Team autonomy Teams that need to decide without each other There is one team; it decides at lunch The main reason to adopt them

If your answers all sit in the third column, you are not buying anything. You are only paying.

What do microservices cost you from day one?

The costs, unlike the benefits, arrive immediately and in full. They don't wait for you to reach the team size that justifies them.

The network becomes a failure mode. In a monolith, calling chargeSender() either works or throws. Across a network it can also time out, succeed but lose the response, succeed twice because your client retried, or succeed after the caller gave up. Every call site now needs timeouts, retries, backoff and idempotency. That is not a library you install, it's a discipline every engineer applies every time, forever.

You lose the database transaction. Inside one database, "debit the sender, credit the ledger, create the payout" is one atomic unit. Split those across services and no such thing exists anymore. You replace it with sagas, compensating actions, an outbox table and a state machine you have to design, build and test.

Eventual consistency becomes a product decision. Once data lives in several stores, "the balance updated but the transaction list hasn't yet" stops being a bug and becomes a question you answer in the UI, in support scripts and in reconciliation logic.

Observability and local development get harder. One stack trace becomes seven logs in seven services, so you build tracing, correlation IDs and centralised logging before you build your product. And an engineer who could have run one app and one database now needs six services, a message broker and a docker-compose file someone has to keep working.

Releases need coordinating anyway. The irony: with one team and five repositories, a single feature spans three of them. You now do multi-repo, version-compatible releases, the exact coordination cost microservices were meant to remove, with none of the autonomy that makes it worth paying.

Costs a distributed system charges from day one: network failure modes, no distributed transactions, eventual consistency, harder observability, slower local development, coordinated multi-repo releases
Costs a distributed system charges from day one: network failure modes, no distributed transactions, eventual consistency, harder observability, slower local development, coordinated multi-repo releases.

Why is this especially brutal in payments?

Take a single transfer. A reasonable-looking decomposition: payments, balances, compliance screening, FX quoting, payouts. Five services.

One transfer now touches all five. The sender's balance is reserved in one, the sanctions check runs in another, the rate is locked in a third, the payout instruction goes to a bank rail from a fourth, and the ledger entry lands in a fifth. No transaction spans them. If step four succeeds and step five times out, money has left an account and the ledger doesn't know it.

In a monolith that scenario does not exist. One transaction, one commit, one rollback. In a distributed system it isn't an edge case, it's Tuesday. And every partial state is a row a human has to look at, because the alternative is a shortfall in a real account belonging to someone waiting for their rent money.

This isn't theoretical for us. GPO-Tech's founder spent two and a half years on Wise's core payment platform team, the team responsible for payment creation and the full payment lifecycle. At that scale the distributed design is correct and necessary. It is also carried by dedicated reconciliation systems, an ops team, mature idempotency and years of accumulated failure handling. That machinery is the price of the architecture, and a four-person startup that copies the architecture inherits the price without the volume that justifies it.

We've since built cross-border crypto-to-fiat transfer products and a real-time market surveillance platform for regulated markets, and the lesson repeats: in money, "almost consistent" isn't a nuance. It's a number with a minus sign in front of it.

What's the strongest argument for starting with a monolith?

Everything above is a cost argument, and cost arguments can be overridden by conviction. This one can't: you do not yet know where your boundaries are.

You'll draw them anyway, because you have to draw something, and you'll draw some of them wrong. Domain boundaries are discovered by building, not by whiteboarding. That isn't a failure of skill; it's what a first version is for.

Concretely: you separate "FX quoting" from "payouts" because they sound like different things. Six months in you learn they aren't. The rate you can honour depends on which rail you route to, and the rail you can use depends on the amount, the corridor and the recipient's bank. Quoting and routing turn out to be one decision wearing two hats.

Now compare the two repair jobs.

Wrong boundary inside a monolith. You move classes between modules, merge two interfaces, fix the call sites. The compiler tells you what broke; the test suite tells you what else broke. One pull request, one deploy, an afternoon or two. Nothing is in flight while you do it.

The same wrong boundary between services. You change two API contracts and version them, because clients run the old shape. You run both versions through the rollout. You migrate data from one service's database to the other's: backfill, dual-write period, cutover. You coordinate a release across two repositories. And throughout, live transfers are moving through the code you're changing. This is a month, and a nervous one.

A wrong boundary inside a monolith is a refactor the compiler checks; the same wrong boundary between services is versioned contracts, a data migration and a coordinated multi-repo release
A wrong boundary inside a monolith is a refactor the compiler checks; the same wrong boundary between services is versioned contracts, a data migration and a coordinated multi-repo release.

Microservices convert architectural mistakes from refactors into migrations. Early on, when your mistake rate is at its highest, that is exactly the wrong trade.

When is a separate service justified from day one?

The honest counterpoint, and it's a real one. Sometimes a service is correct on day one, and the reason is never "the codebase is getting big." It is always isolation.

Reason to extract early Example in a transfer product Why a module isn't enough
Regulated data scope Card data, raw KYC documents and identity images Keeping them out of the main app shrinks what auditors and security reviews must cover
Different reliability target A webhook endpoint receiving settlement callbacks from banking partners It must accept callbacks while the main app is mid-deploy or degraded
Different runtime A fraud scoring model, or a device gateway in a connected product Genuinely different language, hardware or execution model
Vendor-supplied component A third-party screening engine you deploy but don't own You don't control its release cycle regardless

The test is simple. If the answer to "why is this separate?" is tidiness, it should be a module. If the answer is blast radius — security, compliance, availability, or a runtime you can't host in-process — it has earned its network hop.

One caution on the first row: what a licence or a payments regulator actually requires by way of data segregation varies by jurisdiction and licence type, and it changes. Treat architecture as one input and confirm the obligations with a lawyer qualified in the relevant jurisdiction before designing around an assumption.

So what should you actually build?

The real choice was never monolith versus microservices. It's a modular monolith versus an accidental one: a single deployable with genuine internal boundaries, built so extracting a service later is contained work rather than an archaeological dig.

  1. Modules by domain, not by layer. payments, balances, compliance, fx, payouts — not controllers, services, repositories. Layers cut across every feature; domains don't.
  2. One public interface per module. Everything else is internal. If another module reaches past the interface, your future service boundary is already leaking.
  3. No cross-module database access. A module owns its tables; others ask through the interface, never with a join. This single rule does more than anything else to keep a later split possible, because the data is already separable.
  4. Enforce it mechanically. Import linters, build-level module boundaries, architecture tests in CI. A boundary that lives only in a document isn't a boundary. Rules that fail the build are.
  5. Shape calls so they could become async. Prefer commands and domain events over deep object graphs passed between modules. An in-process event bus with an outbox table costs little now and is most of the work of extraction later.
  6. Idempotency keys and correlation IDs from the start, even in-process. They cost a field and pay for themselves the first time you retry anything.

Then split when something real forces you to. The genuine signals: two teams routinely blocked on the same release; a component whose load profile makes you over-provision everything else; a compliance boundary; a reliability target you can point at. The false signals: the codebase feels large, someone wants to try a new language, a conference talk, or an investor asking whether you can "scale."

If your reason to split is a team boundary, then the architecture conversation is really a hiring conversation, which is a different decision with different trade-offs and one we cover in dedicated development team vs in-house.

FAQ

Isn't a monolith going to become a big ball of mud?
Only if you skip the modular part. Mud comes from unenforced boundaries, not from a single deployable. Microservices with sloppy boundaries become a distributed ball of mud, which is the same problem plus latency.

At what team size do microservices start to make sense?
There's no reliable headcount threshold, and anyone quoting one is guessing. The signal is contention, not size: separate teams regularly blocking each other on a shared release, or a component needing different reliability or compliance treatment.

Won't we have to rewrite everything later?
No, if the modules are built properly. Extraction from a well-bounded module is mostly mechanical: put it behind a network interface, move its tables, keep the contract. Rewrite risk comes from monoliths with no internal structure, which argues for modularity, not distribution.

Can a monolith handle real payment volume?
Far more than most first products will ever see. A well-built application with a properly indexed database and read replicas handles volumes that would represent a very successful business. Early scale problems are almost always a missing index or an N+1 query, not the deployment topology.

What about parts that genuinely need isolation, like card data?
Extract those, and only those. A small number of deliberately isolated services around a modular core is a coherent architecture, and different from decomposing the whole product on principle. Check the specific segregation obligations with a lawyer in your jurisdiction rather than inferring them from architecture blog posts.

Investors keep asking about scalability. What do we tell them?
That you optimised for the risk you actually face. The failure mode for an early product is running out of money before finding product-market fit, not running out of servers. A modular monolith ships features faster and keeps the option to split open.

Planning your first architecture?

The architecture decision is worth an hour of conversation before it's worth a diagram. The questions that matter are boring ones: how many people will touch this code in year one, which data is regulated, what has to stay up when the rest is down, and which boundaries you're genuinely confident about.

GPO-Tech builds commercial software and connected products from Tallinn, Estonia, inside the EU and in European working hours. Our payments background is first-hand: two and a half years on Wise's core payment platform team, plus cross-border crypto-to-fiat transfer products and a real-time market surveillance platform for regulated markets. We've built both the distributed version and the modular one, and we'll tell you honestly which your product needs.

If you're putting the build out to tender, our software and hardware project RFP template will get you comparable quotes instead of guesses.

Tell us what you're building and we'll sketch the first architecture with you. Ask a question → or book a call with our engineers →.

GPO-Tech designs and builds connected products and commercial software end to end — from one team in Tallinn, Estonia.

Request a quote →