It is easy to draw a system as six services. It is harder to run six services when a small team owns the product. Each new service brings deployment, monitoring, networking, and data ownership decisions. Those costs are real even before a single customer uses the feature.

Start with the team and the change rate

If one team changes checkout, inventory, and customer accounts together, a single application can be the simplest place to keep those changes coordinated. A shared database transaction is often clearer than a chain of asynchronous calls. The team gets one development setup and one release path.

That does not mean putting every query and rule in one file. Keep modules around business concepts, give them narrow interfaces, and prevent unrelated code from reaching into their tables. A modular monolith lets the team understand what a change touches without paying the operating cost of separate services.

Split only when there is a reason

A service boundary earns its place when the work, ownership, scaling needs, or failure mode is genuinely separate. A long-running image pipeline may need its own workers. A payments boundary may need stricter access and release controls. These are concrete reasons; “microservices scale” is not a plan.

Before extracting a module, document its data ownership and how callers will handle timeouts and retries. Move one boundary, observe the new operational cost, then decide whether another split helps. Architecture is easier to correct when it follows the product's actual shape.

Further reading: Martin Fowler's software architecture guide.

← More in Engineering