A thread on r/devops describes a conference souvenir that shows up in enterprise programmes as well as startups. A working system is declared not "enterprise scale". Months later it is fourteen processes. A login needs several of them. The sprint is YAML, retries, and network traces. The useful reply in that thread is short: a service boundary is an organisational and permission boundary. It is not a synonym for scale, and it is not a synonym for cloud.
Enterprise estates repeat the pattern with more paperwork. A target architecture lands in a slide as a field of boxes because a reference customer, a vendor, or a previous employer ran that shape. The boxes get funded. The seams do not. A year on, a pricing change still requires three releases, a shared database, and a bridge call. You are running a distributed monolith and paying for partitions you did not need.
Independent deployability is the product, not the process count
A service earns its keep when at least one of these is true and you can name it:
- Two groups must ship on different clocks, and the conflict is structural. A missing pipeline template is not structural. A regulatory release window that must not freeze the rest of the product is.
- One workload would force the entire product to scale with it. Inference, bulk import, media, a bursty partner feed.
- A trust boundary is real. Separate audit scope, residency, or a vendor dependency whose stalls must not take down reads of your own data.
If none of those is true, separate processes add partial failure, extra latency, and a second on-call surface. Martin Fowler's MonolithFirst note is old because the mistake is old: distributing before you understand the cuts freezes the wrong ones. Moving a bad cut inside one repository is a refactor. Moving it across a network is a migration, with data, clients, and a rollback that is no longer a git revert.
Practitioner accounts from the last year match that sequence. Teams that mimicked a large-company layout later consolidated and threw away a great deal of glue. A small team that split on a blog post's advice watched coordination and the infrastructure bill move the wrong way, then merged back. The comments that defend services are not wrong. They describe estates with ownership, tracing, and a reason for each process. The absence of that reason is the failure mode, not the existence of the pattern.
What a modular monolith has to enforce
"Modular monolith" is an easy label to claim and a hard boundary to keep. A single deployable with a shared DbContext, public setters on every entity, and any project allowed to reference any other project is a monolith with folders. The modular version makes the seam as real as a service seam would have been, without the socket.
Each module publishes a small surface: application operations, or internal notifications, not its tables. Persistence is private. Another module does not join across those tables to save a query. A CI fitness function fails the build when a forbidden project reference appears. The module's tests boot without the rest of the product. A change that needs two modules says so in the pull request, because the public surface changed, not because a developer reached into a repository class two directories away.
That discipline is what people think they are buying with microservices. The network does not create it. If the discipline is missing inside one process, it will be missing across six, and the six will share a database "temporarily". Temporary shared databases are how distributed monoliths become permanent.
A request that should never have been distributed
Take a commercial order that prices, reserves stock, and writes an audit row. In one process those three steps are one database transaction. The audit row cannot commit if the reservation rolls back. There is no timeout between them.
Split them into Pricing, Inventory, and Audit services and the same click becomes three network calls. Pricing succeeds, Inventory times out, Audit never runs. You now own a compensation problem, a correlation id, and a support desk that has to explain a half-order. None of that complexity came from the business rule. The business rule was a local transaction. The architecture invented a distributed one. If the only reason for the split was a diagram, put the three steps back in one module. If Inventory truly must be a separate product with its own team, the order flow needs an explicit consistency design, not a hopeful chain of HTTP calls. That design is the subject of a later piece in this series. The point here is smaller: do not pay for it by accident.
When extraction is a migration, not a mood
Extract a slice that already owns its data. The new service is the only writer. Callers use an operation or an event. They do not receive a connection string. If the extracted service still cannot complete its own write without a lock in someone else's database, it is not extracted. It is a remote stored procedure.
Do the move once the module boundary has been stable under real changes. A boundary that moved twice inside the monolith will move again after you put a queue between the teams, and the second move will need a data copy. Teams that do reverse a bad split, and several public threads describe that work, move packages first and processes second, one slice at a time. It is viable. It is still a project you could have skipped.
Counts that survive a steering committee
Before you defend a target architecture, count three things on one representative journey:
- How many processes must be healthy for the journey to succeed.
- How many repositories a typical change touches.
- How often a release needs a coordination meeting.
If the first number is "most of the estate" and the second is greater than one, you do not have independent deployability. You have a release train with extra failure modes. Fix the seams, or collapse the processes that always change together. Add a service when a team, a scaling curve, or a trust boundary shows up in the ADR with a name and an owner.
BlackFlow's work on custom enterprise systems usually starts with that count, because the rest of the programme (integration, migration, operability) is cheaper when the boxes match the way the software actually changes.

