From the Technology track

The monolith you should probably keep

A service is earned when a workload's operating characteristics genuinely diverge and a specific team can own that failure domain. Not at a headcount threshold, and never because the codebase feels large. Most of the time what you actually want is a stateless monolith.

The rule I use has nothing to do with size. Not the codebase, not the org.

You split out a service when a specific team needs to own a specific failure domain. That's it.

Both halves are load-bearing. A named team. A boundary where failure can be contained and owned separately.

Start with the sharpest version of "specific failure domain," which is the one you can actually measure.

Workloads with genuinely different operating characteristics: one memory-bound, one CPU-bound.

Run both in a single process and you end up provisioning every instance for the union of the two, which means paying the worst case of each, everywhere, permanently, and that is a cost with an actual number on an actual bill attached to it.

"This team wants to own it" is an organisational preference. Preferences don't survive the operations tax waiting on the other side of a split.

Everything else people cite is a bad trigger. All of it.

"We have thirty engineers now." "The repo is too big." "We want a different language for this part." "Deploys are getting risky."

Every one has a cheaper fix. Reaching for services first means paying an operational tax, permanently and by everybody, to solve what is really a code organisation problem.

What you're actually trading

Microservices trade a code problem for an operations problem, and the exchange rate is worse than it looks.

Inside one process, a call either happens or it doesn't. Two outcomes.

Across a network it can also be slow, partially applied, applied twice, or applied in an order nobody expected. Every one of those becomes a case you have to handle. The handling is code with nothing to do with your product.

You also inherit a category of problem that didn't exist before. Distributed transactions, which mostly you solve by not having them, which means designing around eventual consistency and explaining it to your product people. Debugging that requires tracing, because a request now touches five systems and no single log tells the story. Versioned interfaces between services owned by different teams, and the deployment ordering that goes with them. And a local development story where an engineer needs six things running to work on one.

None of it is unmanageable. All of it is expensive. Paid continuously, by everybody, forever.

So the question is what you get in exchange. If the answer is "cleaner boundaries," you could have had those inside one process for free.

Modular monolith, which is the actual answer for most companies

What most teams actually want when they ask for microservices is boundaries. Clear ownership, changes that don't ripple, the ability to reason about one piece without holding the whole thing in your head at once.

You can have all of that in a single deployable. Modules with explicit public interfaces. No reaching into another module's internals. Ideally, separate schemas or at least separate tables per module with no cross-module joins, because a shared database is what turns any architecture into a monolith regardless of how the code is arranged.

Enforce it mechanically. Conventions don't hold.

Static analysis that fails the build on a forbidden import. Ownership rules requiring the owning team's review. Whatever your ecosystem offers, turn it on.

One deployableThree services
The same boundaries, one of them free to move and the other one a migration.

Do this and you get most of the benefit at almost none of the cost, plus one thing microservices never give you: if the boundary turns out to be wrong, moving it is a refactor rather than a migration. That's the real argument. Early boundaries are usually wrong. Being wrong inside one process costs a day.

The move people forget: make it stateless

There's a third option, sitting between keeping a tangled monolith and breaking it apart into services.

Almost nobody reaches for it, largely because it doesn't have a conference talk attached.

Make the monolith stateless, so that it functions as an engine rather than as a web of business logic. Push state out to the systems designed to hold it, and what's left is something you can run many copies of, kill without ceremony, and reason about one request at a time.

That buys most of what people actually want when they ask for services. Horizontal scaling. A boundary that means something. Deploys that stop being frightening. And it buys all of it without a distributed system underneath, because the copies never need to talk to each other.

So when someone proposes a split, ask this before anything else: is the thing you want from this actually statelessness. If it is, you can have it without the network in the middle, and usually a lot sooner.

It also leaves you well positioned if you do eventually need to extract something. The seam already exists.

Extracting one, when you genuinely need to

When the test is met, extract narrowly, and treat it as a project rather than as a direction.

Pick the piece with the fewest inbound dependencies and the clearest boundary. The ideal first extraction has a genuinely different operational profile: it needs to scale independently, or its failures shouldn't take the main system down, or it carries a completely different security posture from everything around it.

Define the interface first, inside the monolith, and run it as an internal boundary for a while before it ever becomes a network call. That way you find out whether the boundary is right while moving it is still free.

Extract behind a flag, run both paths, compare, then switch. Same discipline as any other risky change. Nothing special about it.

And move the data with it, or you haven't extracted anything.

A service still reading the monolith's tables is a distributed system with all of the costs and none of the isolation. It's the step skipped under time pressure. It's also the one that decides whether the whole exercise was worth doing.

Expect it to take longer than the estimate, and expect the second one to be a great deal easier than the first, because most of what the first extraction cost you was building operational capability that didn't exist yet.

Kubernetes at five people is self-harm

Stated plainly because the question keeps being asked: a five-person startup running Kubernetes has taken on a full-time operational burden to solve problems it does not have.

What it needs is a managed platform. Push code, it runs.

There are several, they're all fine, and the difference between any two of them matters far less than the difference between using one at all and running your own orchestration.

The counterargument is that you'll need it eventually, and migrating later is painful.

Migrating later is painful. It's also much cheaper than eighteen months of a small team's attention, and quite a lot of companies never reach eventually, for reasons entirely unrelated to their infrastructure.

The real cost isn't the setup. Setup is a weekend.

It's that every subsequent problem now has a Kubernetes dimension, and every debugging session runs through a layer nobody on the team fully understands. At five people, that layer is a meaningful fraction of everything you have.

The point at which it starts to be reasonable is when you have someone whose job includes infrastructure, and a real reason (multiple environments, real scale, a compliance requirement) rather than an anticipated one. That's usually somewhere past thirty engineers, and later than most teams think.

Repo layout

Separate question from service architecture. Worth keeping separate, because monorepo and monolith get conflated constantly.

You can run one repo with many services, or many repos with one deployable, and the two questions genuinely don't determine each other.

What I run is mixed, and deliberately so.

Shared platform code, libraries, anything used by more than one team goes in a central repo, because the cost of a change rippling across five repos is what kills shared code.

Product code splits by team. That's where independent movement matters most, and where cross-cutting changes are rarest.

The failure at either extreme is predictable.

One repo for everything, with no ownership enforcement, means every team's build breaks on every team's mistake and nobody owns the tooling.

Repo-per-service means a version bump to a shared library is an afternoon of coordinated pull requests, so nobody upgrades anything and you end up with five versions of everything in production.

So pick per category rather than as a philosophy. Revisit when the pain shows up, not in advance.