Microservices are often treated like the destination of a successful application.
Start with a monolith.
Grow.
Split into services.
Become “modern.”
I think that framing is backwards.
Microservices are not the goal.
The goal is to build a system that can change without forcing the business into a rewrite every time the architecture needs to evolve.
That is what I mean by architectural optionality.
A distributed system is not automatically a better system.
There are good reasons to use microservices:
independent scaling,
independent deployment,
separate security boundaries,
different team ownership,
different runtime needs,
different release cycles.
But distribution also introduces new problems:
network failures
timeouts
retries
idempotency
message delivery
service discovery
versioning
distributed tracing
data ownership
eventual consistency
operational complexityThose costs may absolutely be worth it.
But they should be accepted because they solve a real problem, not because the system reached a certain size.
Scaling does not automatically mean microservices
A common assumption is:
More traffic
↓
MicroservicesBut traffic alone is not enough.
A well-designed monolith can handle a lot.
You can scale application instances horizontally.
You can introduce queues.
You can move files to object storage.
You can add caching.
You can optimize expensive operations.
You can improve database design.
None of those require turning every business capability into a separate service.
A better question is:
Which part of the system actually benefits from independent deployment?
That is much more useful.
Start with boundaries
Suppose an application has:
Customers
Orders
Billing
Reporting
NotificationsIf all of those areas share everything freely, extracting Billing later will be painful.
But if they already have clear boundaries and explicit dependencies, you have options.
Billing can remain inside the monolith.
Or later it can move behind HTTP, a queue, RPC, or events.
That is the difference between:
“We need microservices.”
and:
“We can extract this capability if the evidence says we should.”
The modular monolith preserves choice
This is one reason I think the modular monolith is still underrated.
A good modular monolith gives you many of the architectural benefits people want from microservices:
clear ownership,
explicit dependencies,
isolated business capabilities,
testable boundaries,
easier reasoning,
replaceable implementations.
But you still deploy one application.
You still have local calls.
You still have one operational surface.
That keeps the system simpler while preserving the option to distribute later.
Extraction should happen because something changed
Imagine Billing suddenly needs to process ten times more work than the rest of the application.
Or a separate payments team takes ownership.
Or regulatory requirements demand stronger isolation.
Or Billing needs a deployment schedule that cannot be tied to the rest of the platform.
Now extraction has a reason.
Modular Monolith
Customers
Orders
Billing
Reporting
Notifications
↓
Customers
Orders ──────> Billing Service
Reporting
NotificationsThe architectural boundary already existed.
The deployment boundary changed.
That is healthier than starting with five services because you think the company might grow.
Optionality also applies to language choice
Suppose most of the system is PHP.
Later, one capability develops requirements that strongly favor Go or Rust.
Maybe it is CPU intensive.
Maybe it has a very different concurrency profile.
Maybe another ecosystem has better tooling for that workload.
If the architecture has good boundaries, you should be able to make that choice locally.
PHP Application
|
+---- Orders
+---- Customers
+---- Reporting
|
+---- High-throughput Worker → GoThat does not mean PHP failed.
It means the architecture allowed the team to choose the right tool without rewriting everything else.
Reversibility matters
One thing I increasingly value in architecture is reversibility.
How expensive is it to change your mind?
If choosing a framework, database, deployment model, or integration pattern creates a ten-year commitment, that decision deserves scrutiny.
Sometimes that commitment is necessary.
But where possible, I would rather preserve choices.
That does not mean abstracting everything.
Over-abstraction creates its own problems.
It means putting boundaries where the business already has meaningful boundaries and avoiding unnecessary coupling between them.
This influences EvolvePHP
A major design goal behind EvolvePHP is not:
“Make building microservices easy.”
It is closer to:
Build modular applications now, and make selective extraction possible later.
That is why explicit contracts, component boundaries, execution isolation, interoperability, and modernization matter so much.
A new EvolvePHP application should not need to begin as a distributed system.
The preferred path is:
Modular Monolith
↓
Observe actual pressure
↓
Identify the affected capability
↓
Extract only when justifiedThe same idea applies to existing systems.
Modernization should create options rather than simply replacing one form of lock-in with another.
The real goal
Microservices are useful.
So are monoliths.
So are queues, workers, serverless functions, and separately deployed services.
None of them should become an ideology.
The better question is:
Can our architecture support the deployment model we need when we actually need it?
That is architectural optionality.
And for long-lived software, I think that is more valuable than choosing the architecture that looks most advanced today.
Microservices are one possible destination.
The real goal is preserving the ability to choose.

No comments:
Post a Comment