For a while, microservices became almost synonymous with “serious architecture.”
If an application was expected to grow, the assumption seemed to be that it should eventually become a collection of independently deployed services.
I understand why.
Microservices can give teams autonomy. Different parts of a system can scale independently. Deployments can be isolated. Technology choices can vary between services.
Those are real advantages.
But I think we sometimes skip an important question:
Does the application actually need to be distributed yet?
More often than not, I think the better starting point is still a modular monolith.
That idea is central to how I’m designing EvolvePHP 2.
A monolith is not automatically a mess
When developers hear "monolith," they sometimes imagine this:
Controllers
Models
Helpers
Services
Utils
MoreHelpers
RandomStuffEverything can access everything.
Business rules are spread across the application.
Changing Billing somehow breaks Orders.
Nobody knows who owns a particular database table.
Eventually every new feature becomes harder to implement.
That is a badly structured monolith.
But it is not the only kind of monolith.
A modular monolith might instead look like:
Application
├── Identity
├── Customers
├── Orders
├── Billing
├── Notifications
└── ReportingIt still runs as one application.
It might still use one database.
It may still be deployed as one unit.
But internally, each capability has a clear boundary.
That distinction matters.
Folders do not create modules
It is easy to create this:
Modules/
├── Billing/
├── Orders/
└── Customers/and call the architecture modular.
But if Billing can freely import internal classes from Orders, modify Customer tables directly, and reach into Reporting whenever convenient, the boundaries are mostly cosmetic.
A module should have something it owns.
It should expose deliberate contracts.
For example:
Orders
|
| public contract
v
Billinginstead of:
Billing
|
v
Orders/Internal/WhateverWasConvenient.phpThat means architecture needs rules.
Some relationships should be allowed.
Others should fail tests or validation.
This is one of the things I want EvolvePHP to enforce rather than merely recommend.
Why not start with microservices?
Because distribution creates a completely different class of problems.
Inside one process, this may be straightforward:
$order = $orders->create($data);
$billing->charge($order);Split those capabilities across the network and suddenly we have more questions.
What if Billing is unavailable?
What if the request times out?
What if Billing completed the charge but the response never arrived?
Should Orders retry?
Could the customer be charged twice?
Do we need idempotency?
How do we trace the operation across services?
How do we handle version compatibility?
What if one service deploys before another?
None of these problems are impossible.
But they are real costs.
I don't think teams should pay those costs before there is a reason.
Scaling does not automatically mean microservices
Another argument I often hear is:
"We'll need microservices when we scale."
Maybe.
But scale has several meanings.
More users?
More requests?
More developers?
More data?
More deployments?
One application can serve a lot of traffic by simply running multiple copies:
Load Balancer
|
+---------+---------+
| | |
App App AppAdd caching.
Queues.
Database replicas.
Object storage.
Workers.
There is a lot of scaling available before the application needs to be split into dozens of services.
Sometimes the first scaling problem isn't the architecture at all.
It is one slow query.
The real value is optionality
This is where modularity becomes important.
I don't want EvolvePHP applications to begin with the assumption:
Everything will always remain a monolith.
But I also don't want:
Everything will eventually become a microservice.
Both are predictions about a future we don't know yet.
A better architecture preserves options.
Start here:
Application
├── Identity
├── Orders
├── Billing
└── ReportingThen imagine Billing eventually has very different requirements.
Maybe it needs stricter security controls.
Maybe a dedicated team now owns it.
Maybe it processes much heavier workloads.
Maybe it needs to be deployed independently.
If the boundary was designed properly, the architecture can evolve:
Application
├── Identity
├── Orders
└── Reporting
|
v
Billing ServiceThat is very different from designing Billing as a distributed service on day one because somebody thinks the company might become large someday.
Extraction should be earned
I like this rule:
Extract a service because you have evidence that the boundary benefits from independent deployment—not because microservices look more advanced on an architecture diagram.
Good reasons might include:
independent scaling requirements,
separate team ownership,
security or compliance isolation,
different availability requirements,
independent release cadence,
genuinely distinct operational characteristics.
"Netflix uses microservices" is not one of them.
Your application probably does not have Netflix's problems.
That is fine.
A modular monolith also helps teams
The benefit isn't only deployment simplicity.
Clear modules make ownership easier.
A developer working on Billing should be able to understand:
what Billing owns
what Billing exposes
what Billing depends on
what depends on BillingThat is valuable whether Billing runs inside the same process or on another continent.
In fact, if a team cannot maintain good boundaries inside one codebase, moving those boundaries across HTTP does not automatically improve the architecture.
Sometimes it just turns bad dependencies into network calls.
This matters for modernization too
The same idea applies to older applications.
Imagine a legacy PHP application where everything is tightly coupled.
Before thinking about microservices, perhaps the first modernization step is simply identifying capabilities:
Legacy Application
↓
Customers
Orders
Billing
ReportingThen establish boundaries gradually.
Once those boundaries exist, you can decide what actually needs to move.
Maybe Billing becomes a separate service.
Maybe Reporting moves to a new EvolvePHP module.
Maybe Customers stays exactly where it is.
That is the philosophy behind Evolve Bridge as well:
move what matters; keep what still works.
The goal is not a monolith
This distinction is important.
I'm not arguing that monoliths are always better.
I'm arguing that distribution should be a consequence of requirements, not an architectural starting ideology.
The goal is not:
Build a monolith.
And it is not:
Build microservices.
The goal is:
Build clear boundaries, then let deployment architecture evolve when reality gives you a reason.
That is what I mean by architectural optionality.
For EvolvePHP 2, the direction is straightforward:
Start modular.
Integrate through explicit contracts.
Keep deployment simple while you can.
Extract selectively when the evidence says you should.
Because a modular monolith is not the architecture you settle for before becoming sophisticated.
Done properly, it may be the architecture that gives you the freedom to become sophisticated later—without paying for complexity before you need it.

No comments:
Post a Comment