Blogs

Start with boundaries before services

Service splits work better when the domain boundary is already clear.

I get nervous when a large codebase turns into an automatic microservices discussion.

A service split can help. It can also move unclear ownership into a harder place to debug. If the team lacks a clear domain line inside one codebase, HTTP makes that uncertainty more expensive.

The extra cost shows up as latency, retries, deploy coordination, tracing, and small ownership questions that take too long to answer.

the clean-looking split

Take a normal app:

orders
payments
inventory
shipping
emails

This already looks like five services on a diagram.

So someone splits it:

OrderService -> PaymentService
OrderService -> InventoryService
OrderService -> ShippingService
OrderService -> EmailService

Now placing an order has to coordinate all of them. Payment succeeds, inventory fails, email sends anyway, shipping half-starts, and the order gets a state name that looks like someone panicked.

The code living together was only part of the story. The workflow boundary was still fuzzy, and the service split made that fuzziness more visible.

ownership needs more than folders

Putting code under /services/payment can make the repo look organized before the ownership is actually clear.

A boundary means some part of the system owns data and rules. It can change internally without making every other part change at the same time.

If every change to orders requires changes in payment, inventory, shipping, and emails, those parts are still one workflow split across names.

That can be okay. Calling it honestly helps more than pretending the diagram created independence.

the questions I would ask

Before splitting, I would ask:

Does this part own its data?
Can it make decisions without asking many other parts?
Can it fail without breaking the whole user flow?
Can it deploy without coordinating everyone?
What rule crosses this boundary?

The boundary-crossing rule is the one I would spend the most time on.

Example:

An order should not be confirmed unless payment is captured and inventory is reserved.

If payment and inventory are separate services, this rule now needs retries, reconciliation, maybe an outbox, maybe customer support tools for weird states.

That may be worth it.

The cost needs to be part of the decision.

a modular monolith can be a good step

I think a modular monolith is underrated.

You can keep one deploy and one process while still making boundaries clearer:

app/
  orders/
  billing/
  inventory/

Then enforce simple rules:

orders avoid direct billing table reads
billing exposes methods instead of random structs
shared types stay small
imports stay acyclic

If those rules are impossible inside one repo, adding service boundaries probably means the same thinking problem now has network calls around it.

network calls make unclear boundaries louder

Inside a monolith, a bad boundary is annoying.

Across services, the same bad boundary becomes timeouts, retries, auth, tracing, versioning, deployment ordering, and ownership questions.

A line between two boxes looks clean in a diagram. In production, that line has behaviour and a maintenance cost.

timeout
retry
idempotency key
schema version
trace id
error mapping

So I would rather make the boundary boring in code first: no direct table access from outside the module, clear commands and queries, tests around the contract, and logs around the workflow.

After that, moving it into a service is still work, but at least the shape is known.

when I would split

I would split when ownership is clear, the scale needs are different, the failure mode should be isolated, or the security/audit boundary is real.

Billing may deserve a separate service earlier than profile settings. It has different correctness and audit needs.

For me, “this folder is large” is a weak reason by itself. Large code can be refactored, while unclear boundaries need actual thinking.

Before blaming the monolith, I want to know what belongs together and what rule crosses the line. If that explanation is missing in one process, a new service will mostly give the confusion a nicer URL.