Blog

by Florin Bidirean

Benefits of using middleware over MVC

The traditional Model-View-Controller (MVC) pattern is a tried and tested paradigm for developing web applications. However, recent industry shifts, including growing adoption of async architectures, prove challenging to accommodate in an MVC application. On the other hand, middleware is a high-level architecture that can offer several advantages to MVC, based on your application and development workflow.

Because PHP works with the HTTP protocol, it consumes HTTP requests and produces HTTP responses. Similarly, middleware perform actions based on the request and either completes the response or passes delegation on to the next middleware in the queue (middleware pipeline). Below we will explore several aspects where middleware is superior to MVC and why it's a good idea to migrate to it.

Flexibility and Modularity

  • The middleware architecture is built on a modular approach, since each middleware is designed to handle a specific task, often independent of others.
  • It is easier to modify or extend the request/response flow than for MVC and you can build workflows specific to any given route in your application.
  • Middleware tends to adhere to PHP standards like PSR-7 (HTTP Message interfaces), PSR-15 (HTTP Request Handler interfaces), and PSR-17 (HTTP Message Factory interfaces), defined by the PHP Framework Interop Group.

All of these lead to your codebase becoming cleaner and more easily maintainable in the long run.

Scalability

The modularity of the middleware architecture promotes scalability. Middleware code can be executed by using load balancers, reverse proxies, and edge servers.

Modern middleware stacks often support asynchronous execution which can be used for concurrent requests while reducing the number of threads or resources per request. For this reason, middleware is recommended for microservices or serverless architectures, where different middleware components can be deployed independently of one another.

Reduced Complexity

Again, due to the modularity of middleware, the complexity of the codebase can be reduced compared to MVC. Middleware components can be simpler and more focused on a given task, which promotes readability and maintainability even in complex platforms.

The tangled controller classes ('God controllers') found in MVC often perform too many tasks, like authorizing users, handling forms, formatting the response, and picking views. This can result in duplication across routes. Middleware instead handles one thing only like authentication, CSRF handling, or requests logging. Given the reduced complexity per unit, the logic becomes simpler, there are fewer side effects and less code to rummage around in when debugging and maintaining code.

Performance

While MVC expects each step of its flow to return something because of its controller/action flow, middleware can finish a request-response task faster in certain conditions.

For example, if a certain middleware can fully handle a request, it can return the response early, reducing execution time and computational requirements. The optional view rendering also helps execution time for middleware, while MVC loads e.g. controller factories, view helpers, translation services, and plugins even if you only need a JSON response.

Enhanced Control Over Request Handling

The flow of your application (pipeline) can be much easier to edit without rewriting a bunch of code. This comes from the fact that it's built as a pipeline of isolated, ordered units which makes it easy to rearrange logic without affecting unrelated code. You can inspect, modify, or reject requests at various points in the pipeline. This is particularly handy when processing a request that is dependent on logging, authentication, and authorization.

When you're stepping through a trace or using xdebug, you can identify exactly where you are in the workflow. You can expect the same transparency when you're maintaining the application. You know exactly where you are in the request workflow and can identify if you might need to skip additional execution.

In contrast, MVC has a single entry point and a central dispatcher that follows a strict request-response lifecycle. You can use the MVC event system to customize or extend this rigid workflow without modifying core code. Still, it's difficult to know who’s doing what and when without digging into registered event listeners. One event may trigger multiple listeners in different modules and the order of execution may be unclear.

Middleware Chains

Middleware chains are additional benefits resulting from middleware modularity and enhanced control. They can process a request in a predefined sequence. This is useful for implementing complex processing logic, like data processing, logging, and authentication in a clean and manageable way.

You can define a custom workflow for any given route, based on that route's needs. For example, when dealing with a form:

  • You start with a handler processing form data.
  • Add logging middleware.
  • Add authentication or authorization middleware.
  • Add validation middleware.

A file upload route would use different middleware:

  • You start with the file upload handler.
  • Add authentication.
  • Add file parsing middleware.

This feature satisfies the different needs of each route, while avoiding unnecessary logic and defining each route’s behavior in a clear and predictable way.

Asynchronous Processing

Middleware architectures can help you handle asynchronous processing, which is nowadays increasingly important for building high-performance web applications.

A good example for this scenario is when you need to manage many concurrent connections, while allowing other parts of the application to run in the meantime. This implies that you need to structure your project's logic into independent, composable steps that naturally support non-blocking, async behavior, like when PHP is paired with async libraries. Your middleware has to be coded to await or yield, thus handling its job asynchronously and passing control onto the next middleware.

The downside is that this adds complexity and overhead in the code logic and error handling, making it harder to debug and test, but it can provide benefits in specific cases.

Better Suited for API-First Development

MVC is tightly coupled to the web/MVC context which is difficult to reuse or test your logic in CLI scripts, workers, or other applications. If you need to change one piece of code, it often means you need to change many other interconnected parts, like is the case for templating engines, routing, or service containers. Testing requires you to boot the full framework, because controllers and models are often built specifically for the framework. Controllers often perform multiple tasks, a pattern which violates separation of concerns and makes it harder to understand or delegate logic.

Middleware can often be a better fit for API-first development, where the main interaction with the application is through APIs rather than traditional web pages. This is the recommended approach for handling different types of requests, such as RESTful APIs, GraphQL, and WebSocket connections.

The bottom line is that middleware:

  • Doesn't need view rendering.
  • Is JSON-first.
  • Allows per-route customization.
  • Is stateless.
  • Supports async-friendly communication.

All the above are key items for modern APIs.

Framework Lock-in

MVC has multiple components tied to the framework:

  • Request & response classes.
  • Routing system.
  • Controllers or service containers.
  • Lifecycle.

Conversely, middleware is reusable across currently active frameworks with little or no changes thanks to:

  • Implementing standards like PSR-7 (HTTP Message interfaces) and PSR-11 (Container interface) which also helps integrate with future PHP frameworks that implement them.
  • Decoupling of handlers which means they can be used by API, CLI, worker processes, or unit tests without modification. The middleware handles web-only concerns (authentication, CORS), while the request handler handles business logic, since the latter is pure PHP with no knowledge of HTTP.
  • View rendering is optional or external, since middleware cares about HTTP requests and responses, not about how they are rendered.
  • The middleware pipeline, like the ones provided in Laminas Stratigility or Slim Framework, improves flexibility, customizability, and testability for execution flows.
  • Easier unit testing without bootstrapping the framework thanks to middleware being small, self-contained, and decoupled from the full framework environment.

Implementing migrations

One of the most beneficial advantages of middleware versatility is that it enables you to migrate your codebase progressively. We all know that migrations can be costly and labor-intensive, but middleware allows you to perform the migration one component at a time, with little to no disruption to the overall platform functionality. Basically, the old system is kept in place, while the components are replaced with new implementations using middleware.

This approach enables breaking down large tasks into smaller, more manageable, and easily testable pieces. It's called the strangle strategy (or Strangler Fig pattern). It's a technique used to gradually replace a legacy system with a new one without doing a full rewrite all at once.

A complex migration is thus turned into a series of manageable tasks that can be handled by smaller teams of developers. Its costs can be more easily controlled and spread out over a more reasonable period, which is likely going to be appreciated by investors.

When Is a Migration Mandatory?

More often than not, the migration to a newer architecture is optional, especially if you have the resources to keep your legacy system functional. But what if you must conform to a security framework that details how to secure customer data from various vulnerabilities? Security frameworks may reject old architectures once they are no longer maintained or at the very least will promote the adoption of more modern and robust approaches.

Modern approaches tend to include:

  • Continuous verification for all requests, whether internal or external.
  • Least privilege, meaning the users are allowed the minimum level of access for their specific tasks.
  • Microsegmentation to isolate components with the aim to limit the impact of breaches.
  • Strong authentication and authorization using robust methods.

Normally, it's a task in itself to adapt systems to conform to security frameworks like SOC 2 by being well-maintained, flexible, and by implementing secure coding practices. These tasks are often easier to perform on newer systems built on active components.

Conclusions

In summary, while MVC is still a powerful and proven architecture currently in use by many web applications, the middleware alternative offers additional advantages for modularity, scalability, and performance in specific application requirements.

Additional Resources