Showing posts with label laravel. Show all posts
Showing posts with label laravel. Show all posts

Tuesday, 15 September 2026

Microservices Are Not the Goal — Architectural Optionality Is

Microservices Are Not the Goal — Architectural Optionality Is

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 complexity

Those 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
    ↓
Microservices

But 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
Notifications

If 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
Notifications

The 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 → Go

That 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 justified

The 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.

Tuesday, 8 September 2026

Migrating Legacy PHP Applications to Modern Frameworks: What Actually Changes?

Migrating Legacy PHP Applications to Modern Frameworks: What Actually Changes?

Migrating a legacy PHP application to a modern framework can look deceptively simple from the outside.

You have an old application.

You choose Laravel, Symfony, or another modern framework.

You move the routes, controllers, models, and views.

Done.

Except that is rarely what really happens.

The difficult part of framework migration is not translating syntax.

It is deciding which old assumptions should survive and which ones should not.

That distinction matters because it is entirely possible to move a legacy application into a modern framework and still end up with a legacy system.

A newer framework does not automatically create a newer architecture

Imagine an old application with this structure:

index.php
includes/
functions.php
database.php
users.php
orders.php
payments.php
reports.php

Everything talks directly to everything else.

Business rules are mixed with SQL.

Authentication depends on session globals.

A migration begins.

Months later, the application looks like:

Controllers/
Models/
Services/
Repositories/
Middleware/

That certainly looks more modern.

But suppose the new OrderService still directly updates customer, inventory, payment, and reporting tables.

The folders changed.

The coupling did not.

This is one of the biggest risks in framework migration:

We can modernize the shape of the code without modernizing the architecture.

Start by understanding why you are migrating

Before choosing a destination framework, I think teams should be able to answer:

What problem are we actually trying to solve?

Maybe the current framework no longer supports modern PHP.

Maybe dependencies are abandoned.

Maybe security updates have stopped.

Maybe it is difficult to hire developers who understand the stack.

Maybe the codebase has become hard to test.

Maybe deployments are fragile.

Those are good reasons to modernize.

But each one may lead to a different migration plan.

If the main problem is PHP compatibility, a framework upgrade may be enough.

If the main problem is architectural coupling, changing frameworks without changing boundaries may achieve very little.

Laravel may be the right answer

For many PHP applications, Laravel is a sensible destination.

It has a large ecosystem, good developer experience, strong community support, familiar conventions, and plenty of available developers.

If the goal is to move an application from an unsupported custom framework into a widely understood PHP stack, Laravel can reduce a lot of organizational risk.

The same is true for Symfony in environments that value stronger componentization, explicit architecture, and long-term enterprise use.

There is nothing wrong with choosing a mature framework.

The mistake is assuming the framework will make every architectural decision for you.

It will not.

Migration can happen gradually

A framework migration does not always need a single cutover date.

Suppose an application contains:

Customers
Orders
Billing
Reporting
Notifications

Maybe Reporting is reasonably independent.

Instead of rebuilding all five capabilities before launch, you could move Reporting first.

Conceptually:

Legacy Application
├── Customers
├── Orders
├── Billing
└── Notifications

        |
        v

Modern Reporting

Then observe it.

Run it in production.

Learn from the migration.

Only then decide what should move next.

This is often less exciting than a full rewrite.

It is also usually easier to control.

Data ownership matters more than controllers

One of the hardest parts of any framework migration is deciding who owns the data during the transition.

If both the legacy application and the new application can modify the same business state, things get complicated quickly.

Imagine both systems can create or update invoices.

Now you need to answer:

  • Which system is authoritative?

  • What happens if one succeeds and the other fails?

  • How do you reconcile differences?

  • Can one system understand records created by the other?

  • What happens during rollback?

A cleaner migration usually has explicit ownership.

For example:

Legacy owns Orders
New system owns Reporting

Later that ownership can change.

But it should change deliberately.

Authentication can become another hidden trap

Legacy applications often have years of authentication and authorization behavior embedded in them.

Moving login forms is easy.

Preserving the actual security model is harder.

You need to understand:

  • how identities are stored,

  • how sessions work,

  • how passwords are handled,

  • how permissions are calculated,

  • whether other systems depend on the same authentication state.

A migration should not accidentally weaken authorization simply because the new framework provides a nicer authentication API.

Identity and authorization deserve their own migration plan.

Tests make migration much safer

If the existing application has poor test coverage, I would not necessarily stop everything and attempt complete coverage first.

Instead, protect the behavior you are about to move.

Suppose you are migrating Billing.

Capture the important Billing behavior:

normal payment
failed payment
refund
duplicate request
discount
tax calculation
historical customer case

Then migrate against that behavior.

The tests become a record of what the old system actually does rather than what everyone thinks it does.

That is incredibly valuable in legacy modernization.

Where EvolvePHP fits into my thinking

While building EvolvePHP 2, I have been thinking about framework migration slightly differently.

I do not want adopting EvolvePHP to require the assumption:

“Eventually everything must become EvolvePHP.”

My direction is closer to:

Modernize the capabilities that benefit from moving, and allow the rest of the system to keep working.

That means coexistence matters.

Clear boundaries matter.

Data ownership matters.

Rollback matters.

And interoperability with existing PHP applications matters.

But it is influencing the architecture from the beginning.

Because I think frameworks should help applications evolve—not force businesses into another all-or-nothing rewrite.

Choosing the framework is only one decision

When somebody asks:

“Should we migrate this legacy application to Laravel or Symfony?”

my answer would usually begin with more questions.

What is wrong with the existing system?

Which capabilities change frequently?

Where is the data ownership?

What can be migrated independently?

What must remain available throughout the migration?

How will you roll back?

Only after those questions does framework choice become really useful.

Because my goal is not simply:

Move old PHP into a new framework.

My goal is:

Make the application safer, easier to understand, easier to operate, and easier to change for the next several years.

A modern framework can help enormously with that.

But the real modernization happens in the decisions you make while moving there, and not in the framework name at the end.

Friday, 4 September 2026

Modernizing Legacy Applications in PHP: Considerations and Approaches

 

Modernizing Legacy Applications in PHP: Considerations and Approaches

Legacy PHP applications are everywhere.

Some are ten or fifteen years old. Some are running on unsupported PHP versions. Some use frameworks that are no longer maintained. Others were built without a framework at all.

And many of them are still doing real work every day.

That creates an uncomfortable situation.

The application needs to change, but changing it may be risky.

The temptation is usually to jump straight to a technical solution:

Upgrade PHP.
Move to Laravel.
Rewrite it.
Break it into microservices.

I think modernization should start somewhere else.

With understanding.

First, what does “legacy” actually mean?

Old software is not automatically bad software.

A PHP application written eight years ago may still be stable, understandable and inexpensive to maintain.

Meanwhile, a three-year-old application can already feel impossible to change.

For me, a system becomes a modernization problem when things like these start happening:

  • supported PHP versions cannot be adopted;

  • important dependencies are abandoned;

  • security fixes become difficult;

  • deployments are fragile;

  • developers are afraid to change certain areas;

  • business logic is tightly coupled;

  • testing is weak or nonexistent;

  • infrastructure cannot evolve;

  • new features take increasingly longer to deliver.

Age is only one signal.

The real problem is the cost and risk of change.

Before touching the code, understand what you have

A mature application contains more than PHP files.

It contains business behavior.

It may integrate with:

Payment providers
Banks
Email/SMS services
Government APIs
CRMs
Accounting software
Legacy databases
File storage
Third-party services

Some integrations may not even be documented properly anymore.

Before modernization, I would want answers to questions such as:

  • Which PHP and framework versions are running?

  • Which dependencies are unsupported?

  • Where does authentication happen?

  • Which modules own important business data?

  • Which external systems depend on this application?

  • What are the most frequently changed areas?

  • Which parts are considered dangerous to touch?

  • What tests already exist?

  • What must never break?

Modernization without that understanding can easily turn into archaeology during production incidents.

There is more than one modernization strategy

This is where I think teams sometimes make modernization harder than necessary.

There is no single correct approach.

1. Upgrade in place

Sometimes the best modernization project is simply:

PHP 7.x
   ↓
PHP 8.x

Old dependencies
   ↓
Supported dependencies

The application's architecture remains mostly unchanged.

That can still deliver major benefits:

  • security support,

  • performance improvements,

  • modern tooling,

  • easier hiring,

  • access to maintained libraries.

Not every modernization project needs a new framework.

2. Refactor gradually

Another approach is to improve the existing application while it continues running.

For example:

Direct SQL
   ↓
Repository / persistence boundary

Global state
   ↓
Explicit dependencies

Large controller
   ↓
Application services

This can be slow, but it keeps changes small.

It also allows the team to learn about the application before making larger architectural decisions.

3. Establish module boundaries

A tightly coupled application may benefit from becoming a modular monolith before anything is extracted.

Perhaps the system currently looks like:

Application
   ↓
Everything depends on everything

The modernization target could be:

Application

├── Customers
├── Orders
├── Billing
├── Reporting
└── Notifications

Still one application.

Still one deployment.

But now responsibilities and dependencies are clearer.

That alone can make future modernization much easier.

4. Replace one capability

Sometimes one part of the application is causing most of the pain.

Maybe Reporting cannot scale.

Maybe Billing has become too risky to change.

Maybe a legacy authentication system needs replacing.

Instead of rebuilding everything:

Legacy Application
├── Customers
├── Orders
├── Billing
├── Reporting
└── Notifications

you might gradually reach:

Legacy Application
├── Customers
├── Orders
├── Billing
└── Notifications

        |
        v

Modern Reporting Capability

The business keeps operating while one boundary evolves.

This is often much easier to control than a full replacement.

5. Rewrite

Yes, rewrites still belong on the list.

There are systems where replacement is genuinely the right choice.

For example:

  • the application is relatively small;

  • requirements are well understood;

  • data migration is straightforward;

  • the current technology cannot support future needs;

  • maintaining the old code costs more than replacing it.

But a rewrite should be an evaluated option.

Not the default reaction to code we dislike.

Framework migration deserves the same caution

Moving from an old PHP framework to Laravel, Symfony or another modern framework can be valuable.

But framework migration does not automatically solve architecture problems.

If tightly coupled business logic is moved unchanged into a newer framework, you may end up with:

a modern framework containing a legacy architecture.

The syntax changed.

The underlying problem did not.

I think modernization should first identify business boundaries and ownership.

Framework choice comes after that.

Data usually determines how difficult the migration really is

Applications can often tolerate temporary duplication of code.

Data is much less forgiving.

Questions quickly appear:

  • Which system is the source of truth?

  • Can both old and new systems write?

  • How are historical records handled?

  • How do we validate migrated data?

  • Can the migration be reversed?

  • What happens during partial failure?

This is why I strongly prefer explicit ownership during incremental modernization.

At any point, somebody should be able to answer:

Which system owns this data and operation right now?

Ambiguous ownership creates very expensive bugs.

Testing changes the risk completely

Modernizing an application without tests is possible.

But every change carries much more uncertainty.

One practical strategy is not to attempt perfect test coverage first.

Start around the behavior you intend to change.

Capture what the legacy system currently does.

Then modernize against that evidence.

Tests become a safety net for discovering undocumented behavior.

Sometimes a test that exposes a strange legacy rule is more valuable than immediately “cleaning up” that rule.

Modernization should have a rollback story

Every migration plan naturally explains how to move forward.

Fewer explain how to move backward.

Before replacing a capability, I want to know:

How do we cut over?
How do we observe it?
How do we know it is working?
How do we return safely if it isn't?

If rollback is impossible, that risk should at least be explicit.

Modernization should reduce uncertainty, not hide it.

Start with the problem, not the destination

This is probably the principle I come back to most.

Don't begin with:

“How do we migrate this system to Framework X?”

Begin with:

“What prevents this application from changing safely?”

Maybe the answer is the framework.

Maybe it is PHP version compatibility.

Maybe it is database coupling.

Maybe it is missing tests.

Maybe it is deployment.

Maybe it is one badly designed subsystem.

Once that is clear, the modernization strategy becomes much easier to reason about.

Because modernization is not really about making old PHP look new.

It is about making a valuable application safer and cheaper to change.

And sometimes the best modernization decision is surprisingly small.

Friday, 31 October 2025

The Ultimate Laravel Setup for VS Code: Free Extensions to Rival PHPStorm

 

Hey everyone,

I want to put this out there for anyone looking for the ultimate Laravel setup in VS Code. These extensions enable you to experience a superb development integrated environment that can be compared to paid versions like PHPStorm.


 

If you're new to Laravel development or need a robust extension setup, these will give you the required effect. They're all free to use!

Here's the list:

  • GitHub Copilot Chat - Microsoft
  • Auto Close Tag - Jun Han
  • Auto Complete Tag - Jun Han
  • Better PHPUnit - Calebporizo
  • Code Spell Checker - Street Side Software
  • Container Tools - Microsoft
  • Docker DX - Docker
  • DotENV - Mikestead
  • ESLint - Microsoft
  • GitHub Copilot - GitHub
  • Laravel - Laravel
  • Laravel Artisan - Ryan Naddy
  • Laravel Blade Formatter - Shuhei Hayashibara
  • Laravel Blade Snippets - Winnie Lin
  • Laravel Docs - Austen Cameron
  • Laravel Intellisense - Mohamed Benhida
  • Markdown Preview Enhanced - Yiyi Wang
  • Markdownlint - David Anson
  • PHP Intelephense - Intelephense
  • PHP Namespace Resolver - Mehedi Hassan
  • Prettier Code Formatter - Prettier
  • Tailwind CSS Intellisense - Tailwind Labs
  • Thunder Client - Thunder Client
  • Vue - Vue

Let me know what you think and how it has helped you in the comments below!

#Laravel #VSCode #WebDevelopment #FreeTools