http-interop / http-middleware

PSR-15 interfaces for HTTP Middleware
MIT License
73 stars 7 forks source link

Alternative proposal #68

Closed designermonkey closed 7 years ago

designermonkey commented 7 years ago

I've re-read everything that was discussed in my last issue #46 and I realise that having an example of an alternative that fixes my myriad of concerns over swapping one flawed implementation for another (no offence meant), is preferable over me just offloading my confusing concerns.

I can clearly see my previous issue asked one question, then rambled about other aspects of my concerns, so in an attempt to separate concerns (see what I did there?) I will focus on a single response to this proposal.

An Alternative Approach to Middleware

I can see why a single pass approach is preferable to the double pass, but in my mind, they are still both flawed and violate the single responsibility principle and also the interface segregation principle.

In both cases, the middleware is allowed to know too much about the entire process of accepting a request, and expecting a response; whether that be the double pass having a pre baked response instance, or the single pass being told about the 'next' item in the chain.

A Middleware should only care about what it is supposed to do in the simplest way possible, enforcing the SRP.

I am proposing that we really look at what the requirement is here, and come up with something that fits the bill properly, rather than rehash previous principles that smell bad. Here is something I started to touch on in my issue, and that @schnittstabil has also touched on.

<?php

namespace Psr\Http\Middleware;

use Psr\Http\Message\ResponseInterface;

interface MiddlewareInterface
{
}

We need a base interface that can allow type hinting a single point of adding middleware to a queue for example.

<?php

namespace Psr\Http\Middleware;

use Psr\Http\Message\ResponseInterface;

interface ResponseFactoryInterface
{
    public function createResponseInstance(): ResponseInterface;
}

We need an interface to allow implementers access to a factory method to provide whatever implementation of a ResponseInterface is desired for that Middleware.

<?php

namespace Psr\Http\Middleware;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;

interface RequestMiddlewareInterface extends MiddlewareInterface
{
    public function parseRequest(RequestInterface $request): MessageInterface;
}

We have an interface that provides Middleware for the inbound flow of a stack. Also note that to allow the Middleware stack to be short circuited, we only type hint a response to be MessageInterface. This means, normal behaviour for the stack is to proceed on a returned instance of RequestInterface, and if an instance of ResponseInterface is given, stop the inbound run of the stack.

<?php

namespace Psr\Http\Middleware;

use Psr\Http\Message\ResponseInterface;

interface ResponseMiddlewareInterface extends MiddlewareInterface
{
    public function parseResponse(ResponseInterface $response): ResponseInterface;
}

We have an interface that provides middleware for outbound flow of the stack. This allows us to have middleware that can, for example, change the cache headers of the response based on business rules.

<?php

namespace Psr\Http;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

interface ExchangeInterface
{
    public function exchange(RequestInterface $request): ResponseInterface;
}

We should also be opinionated on how a request is exchanged with a response, even if only to say "Hey I give you a request, you give me a response!". This would lie a level above in the namespace of course, being that it is not specific to messages or middleware.

Why Another Proposal

I can picture faces of interest and faces of concern over this idea. Let me explain some more detail.

We are violating the SRP and ISP in both implementations thus far, and we need to reimagine this as above to prevent that violation. Let me write the middleware use cases I've seen so far:

  1. I want to make a Middleware to handle requests.
  2. I want to make a Middleware to alter a response.
  3. I need to make an instance of a response to short circuit the exchange.

These are fundamentally three separate responsibilities, and must be separated. There is no valid argument against this, sorry to annoy anyone there, but that's the truth. To fix this from both current implementations, we segregate the interfaces, therefore separating the concerns.

Add to the above the fact we let the Middleware itself choose whether to continue the stack or not, by providing a callback in the form of 'next', we have a messy situation that needs cleaning up.

Why do we allow a Middleware to handle both inbound and outbound flow in one function, and also pass in a hook to the outside world? That hook, if implemented badly could allow a Middleware to cause all sorts of havoc by accessing things it should never know about!

No, let us be strict here and not let the Middleware know anything about the outside world, other than the request/response (dependent on interface) and it's single responsibility.

Benefits of the above proposal

There are the obvious ones being separation of concern and interface segregation, I think I've covered those.

Also, please take note that I have specifically not used ServerRequestInterface. There is no reason at all that this implementation would not function in a client middleware environment also. Why repeat things by waiting for a client middleware proposal when one will do?

Wrap Up

As I've said before, this is no insult to anyone who has put work in so far, I am just coming at this from a fresh perspective. Please discuss this as I fear for this being finalised as another incorrect design that will be around for years to come; I never liked the double pass, and I don't like the single pass either. Something better is needed.

shadowhand commented 7 years ago

This is not the right place for suggesting a completely new direction for PSR-15. If you actually believe in this proposal, take it to the FIG mailing list and I will reply there.

designermonkey commented 7 years ago

Done: https://groups.google.com/forum/#!topic/php-fig/vCKxyraoqnc