Thursday, 24 September 2026

Why Frameworks Should Help You Leave Them

Why Frameworks Should Help You Leave Them

A framework is supposed to help you build faster.

Routing, dependency injection, database access, queues, validation, authentication, caching, testing support—good frameworks remove huge amounts of repetitive work.

But there is another question I think framework design should take seriously:

What happens when the application eventually needs to move beyond the framework?

That might sound strange.

Why should a framework make it easier to leave?

Because long-lived software changes.

And if a framework becomes impossible to separate from the application, it can eventually become part of the problem it once solved.

Framework lock-in is not always bad

Some lock-in is reasonable.

If you choose a framework, you are choosing conventions, APIs and abstractions.

You should benefit from them.

Trying to hide every framework feature behind another abstraction often creates unnecessary complexity.

I don't think developers should build applications as if they are planning to replace their framework next Tuesday.

But there is a difference between:

“This application uses Laravel.”

and:

“This application cannot express its business rules without Laravel internals.”

The first is normal.

The second becomes expensive when the system gets old.

Business logic should survive framework change

Imagine a billing rule:

Customers on the Enterprise plan
receive a 7-day grace period before suspension.

That rule might live for ten years.

During that time, the application could move through:

Framework version A
      ↓
Framework version B
      ↓
New database layer
      ↓
Queue redesign
      ↓
Different runtime
      ↓
Partially extracted service

The business rule should still mean the same thing.

If it is deeply buried inside controller methods, ORM lifecycle hooks, global helpers and framework events, understanding or moving it becomes much harder.

The framework should help execute the business logic.

It should not become the business logic.

Good boundaries make migration boring

Suppose Orders depends directly on dozens of framework facilities:

Controller
   ↓
ORM model
   ↓
Global helpers
   ↓
Static auth state
   ↓
Framework event bus
   ↓
Framework queue

Now imagine extracting Orders into another process.

You do not just move Orders.

You move all the assumptions surrounding it.

Compare that with:

HTTP
  ↓
Order Application Service
  ↓
Order Domain
  ↓
Explicit Interfaces
  ↓
Framework Adapters

The second design does not guarantee painless migration.

Nothing does.

But it gives you places where change can happen.

The database implementation can change behind a contract.

The queue transport can change.

The HTTP adapter can change.

The application rules remain easier to reason about.

That is architectural optionality.

Frameworks should expose boundaries, not hide everything

One temptation in framework design is to make everything feel magical.

Auto-discovery.

Global state.

Implicit dependencies.

Automatic bootstrapping.

Invisible lifecycle behavior.

These features can make the first week incredibly productive.

But implicit architecture becomes harder to understand after several years.

I prefer frameworks that make important boundaries visible.

For example:

Who owns this service?
When is it created?
How long does it live?
Who resets it?
Who owns this data?
Which module depends on which?
What happens if this capability moves elsewhere?

You do not need ceremony everywhere.

But the things that affect long-term architecture should be understandable.

Interoperability matters

One way frameworks can reduce lock-in is by respecting ecosystem standards.

In PHP, PSR interfaces are useful partly because they create shared boundaries between libraries and frameworks.

If your HTTP components, logging, caching or container integration can speak common interfaces, replacing one implementation becomes more realistic.

Interoperability also matters when integrating existing systems.

A modernization project should not always require:

Old Application
      ↓
Complete Rewrite
      ↓
New Framework

Sometimes the better path is:

Existing Application
      ↔
New Capability
      ↔
Gradual Migration

The ability to coexist can be more valuable than the ability to replace.

Leaving does not always mean replacing the whole framework

This is important.

An application does not need to “leave” a framework completely.

Maybe only Reporting needs to move.

Or Notifications.

Or a high-concurrency worker.

Or a payment capability that needs stronger isolation.

A mature system may eventually look like:

Main PHP Application
├── Customers
├── Orders
├── Admin
└── Catalog

Separate Services
├── Reporting
└── Notifications

That is still a framework application.

It simply stopped requiring every workload to live inside the same runtime.

A framework that supports healthy boundaries makes this much easier.

Upgrades are also a form of leaving

There is another kind of migration developers sometimes overlook.

Moving from one major framework version to another can resemble a small migration.

APIs disappear.

Configuration changes.

Packages become incompatible.

Runtime assumptions change.

If application code is tightly coupled to every framework detail, even upgrades become dangerous.

That is why good architecture can help even if the framework is never replaced.

You are preserving the ability to move between versions of the same ecosystem.

This influences how I think about EvolvePHP

One principle I keep returning to while building EvolvePHP is:

The framework should not require permanent architectural dependence in exchange for short-term productivity.

That influences several areas:

  • explicit module boundaries,

  • contracts around infrastructure,

  • interoperability,

  • execution lifecycle visibility,

  • incremental modernization,

  • Bridge-based coexistence,

  • selective extraction.

The goal is not to make EvolvePHP irrelevant.

The goal is to make applications stronger than their dependency on EvolvePHP.

If a future team decides that one capability belongs somewhere else, the architecture should help rather than resist them.

The best framework relationship should not feel like captivity

A framework can provide enormous value for many years.

It can shape how teams work.

It can reduce complexity.

It can make good practices easier.

But the application ultimately belongs to the business, not to the framework.

That means the architecture should preserve some freedom.

Freedom to upgrade.

Freedom to replace an infrastructure component.

Freedom to extract a module.

Freedom to integrate another runtime.

And, if necessary, freedom to migrate away completely.

A framework that makes leaving possible is not weakening its value.

It is respecting the lifetime of the software built on top of it.

No comments:

Post a Comment