Which pressures actually justify separate services?
A modular monolith is usually the better choice when one team owns the product, important operations need to remain consistent together, and the business is still discovering its boundaries. Choose microservices when independent releases, distinct scaling needs or stronger runtime isolation solve a demonstrated problem, and the team can operate the resulting distributed system.
Separate two architecture decisions
Modularity describes how responsibilities are organised. Deployment describes which parts are released and run together. A modular monolith has explicit internal boundaries but ships as one application. Microservices move selected boundaries across a network and give them separate deployment lifecycles. A product can have disciplined modules without turning each one into a service.
Ask what independence would change
Before proposing a service, finish this sentence: this capability needs to be deployed, scaled or isolated separately because of this observed constraint. Name the release delay, workload or failure consequence. If the answer is only future growth, keep the boundary inside the application and record what evidence would justify revisiting it. Our recommendation is to make deployment independence earn its cost.
What makes a monolith genuinely modular?
A modular monolith needs owned business capabilities, explicit interfaces and restrictions on which code may change which data. Folder names alone do not provide these boundaries. A useful test is whether a team can change the implementation of one module while its callers continue using the same small public contract.
Give business rules an owner
In an illustrative booking product, availability owns whether a slot can be reserved, bookings owns the reservation lifecycle, and billing owns the internal invoice state. Other modules ask for a business operation instead of editing those records directly. A shared database can be practical, but unrestricted cross-module writes turn it into shared implementation knowledge.
Make the boundary checkable
Use package visibility, dependency rules or architecture checks to prevent imports into another module's internals. Keep module-specific tables and migrations under an explicit owner. Review cross-module queries and reporting deliberately. A shared library should contain stable common concepts, not become the place where every business rule accumulates. These are implementation choices to fit the stack, not a requirement for an elaborate framework.
How should founders compare the operational trade-offs?
Compare the work required to deliver and support one important customer journey under each approach. A shared deployment reduces the number of release and communication boundaries, but couples application releases and some failures. Separate services can give teams independence, while adding responsibility for network failures, compatibility and work that completes across multiple systems.
Use four pressure tests
Review ownership: can distinct teams maintain and support the capabilities independently? Review change: do unrelated releases repeatedly block each other? Review load: does one capability have a measured resource profile that makes whole-application scaling wasteful? Review containment: must one capability remain available when another process fails? A concrete pressure can justify a service; there is no universal score or team-size threshold.
Budget the whole operating model
Microsoft's architecture guidance identifies independent deployment and scaling as benefits, alongside added complexity in communication, data consistency, testing and management. Include those responsibilities in the estimate. Someone must own service credentials, deployment pipelines, compatibility, monitoring and recovery. Buying managed infrastructure can reduce platform work, but it does not settle your product's business-state rules.
What does the choice look like in a booking product?
Consider a small team building a booking product whose reservation rules still change frequently. We would start with booking and availability modules in one application, then treat document generation or another heavy background workload as a separate decision. This keeps the core transaction understandable while leaving room to isolate a capability with different operating needs.
Keep one business promise consistent
Suppose confirming a reservation must also consume the last available slot. If the records share a transactional database, the application can commit those changes together with appropriate concurrency controls. Moving availability into a separate service means the workflow needs an explicit protocol for reserving, confirming and releasing capacity. A local transaction still cannot atomically include an external payment provider; that boundary needs reconciliation under either architecture.
Extract the unusual workload when evidence supports it
If generating large customer documents repeatedly exhausts application memory, first measure the cause and consider bounded background work. A separate worker may then provide sufficient isolation without decomposing the entire product. Give it a clear input contract and visible job state. Calling that worker a microservice matters less than whether the separation removes the demonstrated constraint.
When should the team revisit a modular monolith?
Revisit the choice when a stable capability has a clear owner and a persistent need for independent operation. Frequent coordinated edits across a proposed boundary suggest that the boundary may be wrong. Extract one capability only after its data ownership, callers, failure behaviour and transition plan are understood well enough to preserve the product's promises.
Treat extraction as a product change
Define who owns the data after extraction, how callers handle unavailability, which versions can coexist and how in-flight work is reconciled. Replace direct dependencies with a contract before moving the implementation. Internal modularity helps, but it does not make a network boundary free or guarantee that a later extraction will be easy.
Write a decision record with a revisit trigger
Record the current choice, the alternatives, the constraints and the evidence that would change the answer. For the booking example, the trigger might be repeated release contention between separate owning teams or a measured workload exceeding the agreed resource budget. A modular monolith can remain the long-term architecture if those pressures never appear.
Bring constraints to the architecture discussion
Twofold's architecture and discovery work can help map ownership, transactions and release needs before a service split. Bring a representative workflow, recent release friction and workload evidence. The useful output is a justified set of boundaries and a review point, rather than a target number of services.

