Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce scope and simplify PSR #6

Open
mindplay-dk opened this issue Apr 26, 2017 · 12 comments
Open

Reduce scope and simplify PSR #6

mindplay-dk opened this issue Apr 26, 2017 · 12 comments

Comments

@mindplay-dk
Copy link

mindplay-dk commented Apr 26, 2017

I'd like to suggest we reduce the scope of the PSR to dealing with server-side concerns.

There are client concerns (mainly async) which are not addressed by the interfaces defined in the current proposal - this is first of all a can of worms, and secondly, there's no real reason not to push this off to a separate PSR, assuming there's any interest or value in such a PSR in the first place.

I have no particular interest in the client-side interfaces myself - but I have a strong interest in seeing the server-side abstractions standardized, as soon as possible, e.g. before PSR-15 passes; which, if it passes first, will include an identical delegate interface to proxy middleware. It would be better if this interface was standardized first, such that someone could dispatch SPR-15 middleware by passing any custom handler as delegate, such that a single middleware component could be more easily invoked directly without the need to create a dedicated pipe/dispatcher, in some cases, just to dispatch a single middleware component.

I see no particular reason why the server and client interfaces can't be separate PSRs - it's probably a lot easier to manage and discuss, as asynchronous client operations are radically different and more complex than synchronous server operations, and likely a much smaller community is interested in the details of that.

I would therefore propose to first of all reduce the scope to the three primary server-side operations:

  1. Request -> Response
  2. Request -> Request
  3. Response -> Response

By reducing the scope to server-operations only, we can drop the "server" qualifier from interface-names.

In the following, I'm going to propose more "neutral ground" interface-names that I think describes these operations more in the abstract.

I'm also going to propose real method names, for several reasons:

  1. __invoke is magic, which does not belong in an interface.
  2. Using identical method-names in every interface prevents a component from implementing more than one interface.
  3. Anonymous classes accomplish the same thing as callables, but allow for static analysis in tools and IDEs.
  4. Naming every method __invoke creates ambiguity at call-sites in client code.

The latter point is important, because it removes the only practical reason to use __invoke in the first place: you don't need callables for this, and they are actually inferior to anonymous classes, which accomplish the same thing - granted, with slightly more verbosity, but that's merely cosmetics.

The proposed method-names will include both a verb and a noun, for several reasons:

  1. Clearly indicates both the subject and object of each action.
  2. Prevents ambiguity between method-names of individual interfaces of this PSR.
  3. Prevents collision with common method-names in frameworks and other PSRs.

I'm essentially trying to reduce the scope to cover the expected flow of operations in any given server-side web application to e.g.:

  1. Operations on the request prior to processing,
  2. The actual processing of the request and creation of a Response, and
  3. Operations on the request after processing.

I'm trying to define a terminology that describes the above as a domain first, and as isolated operations secondly - that is, the terminology should be pervasive and consistent throughout the domain described by the PSR, but the individual facts should make sense when considered in isolation as well.

Request -> Response

This is the most important interface of them all, so it's really important to get the terminology right.

The term "action" collides head-on with controller/action terminology - it's a term that's already used in practically every framework in existence, so I really think we should stay away from that.

In the very abstract, something that handles a request is probably just a "handler", and in the abstract, a handler processes a request, so I would propose:

interface HandlerInterface {
    public function processRequest(ServerRequestInterface $request): ResponseInterface;
}

Request -> Request

This interface (like the Response -> Response operation) describes operations performed on the request prior to processing, e.g. a "filter" for pre-processing. (per dictionaries, "a piece of software that processes data before passing it to another application".)

The term "operator" collides with ordinary programming terminology, and attempts to describe in the very abstract what it is, rather than what it does - because this interface describes a facet and not an abstract entity in it's own right, the interface name needs to reflect the role of the class that implements it.

In the abstract, something that filters a request prior to processing is probably just a "filter", but to avoid collision with the response filter, we need to qualify the interface name, so I think "request filter" describes this well, and in the abstract, a request-filters filters a request, so I would propose:

interface RequestFilterInterface {
    public function filterRequest(ServerRequestInterface $request): ServerRequestInterface;
}

Response -> Response

Similar to the Request -> Request operation, this interface describes operations performed on a response after processing, e.g. a filter for post-processing.

Likewise, in the abstract, this is probably just a "filter", e.g. a response-filter filters a response, so I would propose:

interface ResponseFilterInterface {
    public function filterResponse(ResponseInterface $response): ResponseInterface;
}

Footnote:

I wrote these out with PHP 7 return type-hints just for the sake of discussion, but with the understanding that these may need to be defined using @return doc-blocks instead.

And please don't dismiss me by crying "bikeshedding" - I'm not married to these exact names; I've provided a list of criteria for a naming strategy for the types and methods of this domain, and you're of course welcome to challenge those rationale, but I'm not merely trying to put my name on this, I'm trying to ensure that the type- and method-names make sense in every conceivable context and as a whole.

HandlerInterface::processRequest could possibly be reduced to process(), as per DelegateInterface in PSR-15, I'm not sure about that? I guess it depends on whether PSR-15 is going to barrel ahead with it's own interface-declaration or adopt and depend on the handler-interface proposed by this PSR - I'm hoping it will adopt this, because having two identical interfaces in two very closely related domains is going to create practical problems; it's very likely to prevent interoperability in numerous cases where interoperability would have been straight-forward. (Imagine the absurdity of having to write a DelegateInterface adapter for a HandlerInterface implementation.)

Anyhow, there's my proposal. Look forward to hearing your thoughts.

@schnittstabil
Copy link
Member

Sorry @mindplay-dk, I believe that is too much for a linear discussion at github.

First things first: Reduce scope #7

@mindplay-dk
Copy link
Author

@schnittstabil I'm not actually proposing much, e.g. no practical change to any of the proposed interface signatures - I'm mostly providing a really thorough account of the reasoning behind a consistent terminology and naming strategy; I can't do that without addressing all three interfaces at the same time.

As for the reduction in scope, this would work with or without that - but I beelieve this PSR would also work just as well without the additional interfaces; I don't think there's any practical reason those can't exist in a separate PSR from this one, and that's a secondary point of this issue, but yeah, maybe that should be addressed separately in #7.

@schnittstabil
Copy link
Member

I believe PSR documents should evolve in an iterative and incremental process. Every aspect of the documents is worth being questioned and discussed – code is read much more often than it is written, and this is even more true for standards.

Currently, the documents reflect my personal opinionated decisions – I don't like that all, standards shouldn't reflect the opinion of a single person. IMO, the only way out, is to work in public, considering every feedback seriously and document the decision-making.

Therefore, my goal are not these interfaces only. I want also:

  • github issues and pull request, which document the reasons behind
  • a meta-document, which summarizes reasons
  • at least 3 use cases proving the usefulness of an interface [*]
  • some reasonable amount of use cases proving that the interface/mehtod names make sense [*]

[*] before acceptance vote

Your proposal consists of multiple changes:

  1. remove client-side interfaces
  2. remove the Server prefix of some interface names (this probably implies a change of the namespace)
  3. change the name of [Server]ActionInterface to HandlerInterface
  4. change the name of [Server]RequestOperatorInterface to RequestFilterInterface
  5. change the name of ResponseOperatorInterface to ResponseFilterInterface
  6. change the method name of 3. from __invoke to process[Request]
  7. change the method name of 4. from __invoke to filterRequest
  8. change the method name of 5. from __invoke to filterResponse

All of those changes are based on reasons, but my decisions were based on reasons too. Thus, changing everything in one step would be as opinionated as it was in the first place. I can remember a discussion about the namespace of PSR-15 (after dropping client-side middlewares) for example, which justifies that change. I want to have some similar kind of meta-documentation for this PSR.

I'm trying to ensure that the type- and method-names make sense in every conceivable context and as a whole.
I can't do that without addressing all three interfaces at the same time.

This makes sense to a certain extent and I think we can discuss some changes at once. I therefore suggest to discuss better names for [*]OperatorInterface next – that may have an impact on Action vs Handler discussions as well (consistent naming etc.).

@mindplay-dk
Copy link
Author

Okay, so, focusing on the "operator" term.

As said, I think "operator" is a bit ambiguous with programming language terminology - but more importantly, it sounds like it describes a type, which seems to suggest it be implemented as a class, as opposed to describing a role, which seems to suggest it be implemented as a facet of a class.

Rather than implying a class/interface pair, I'm trying to imply a behavior - an implementation can act as a filter in a given context, it doesn't necessarily need to be a filter.

One source defined the term filter as "a piece of software that processes data before passing it to another", which seems like a reasonable fit both for pre-processing of a request and post-processing of a response, which I think is what we're essentially doing with these interfaces: you wouldn't need to filter a request unless you were expected to also process the request, and you wouldn't need to filter a response unless you also processed the request.

In other words, given a request which leads to the creation of a response, these three interfaces minimally describe the request/response life-cycle as before, during and after processing a request into a response.

I don't think you can meaningfully distill it any further, though one of my coworkers disagrees with me - he thinks pre/post-filters can be described as handlers, which, in principle, they can, but the pre/post-filters have a meaningful advantage in situations where a component is only interested in dealing with a request or response, which is that you don't need to deal with the other at all.

That is, a request-filter doesn't need to delegate a response back to it's caller, and a response-filter doesn't need to consider an incoming request.

In other words, I believe these interfaces make sense in situations where only request or response is relevant to the function being performed, whereas, if implemented as handlers, you would have unnecessary dependencies on either a request or response implementation to satisfy parameter and return type-hints, which I think points to something being "off".

Since pre- and post-filters can be described more simply than handlers, I think that these two interfaces are meaningful, although, admittedly, I have a hard time citing examples - I think, at least in part, because we haven't had those abstractions and therefore have had to find other ways to deal with those situations.

I do have a use-case for post-filters, which I'm actively using in one project - they provided a very elegant and decoupled way to implement various "finalizations" of a response, such as committing a session ID or other application-generated cookie to the response. (It's hard to describe in the abstract, but that project is proprietary, so it's the best I can do at the moment.)

I think that's my entire reasoning for the filter interfaces. In a nutshell, I've found that I need them. As to whether they're important for interoperability between frameworks? I'm not certain. I found a pressing need in my application architecture, though, at this time, perhaps I have not seen a pressing need to share implementations of pre/post-filters, personally. I expect that we would find need though - or, at least, situations in which a pre/post-filter is a better fit than a handler.

@mindplay-dk
Copy link
Author

@schnittstabil How would you feel about standardizing on the handler-interface first, in isolation, with no other interfaces being part of the first specification?

The issue is that standardization of the handler-interface is currently blocking PSR-15, which, if standardizing on this interface takes too long, is likely to move ahead with the current proposal as-is, which would be very harmful to interoperability in the long run.

I've written about this at length, most recently here.

The handler-interface can stand on it's own just fine - the other interfaces don't strictly need to be part of the same proposal, and if we could get this one to move ahead quickly, we can finalize PSR-15 by substituting it's identical delegate-interface for this, which will greatly help interoperability overall, now, and in the future.

I'd rather not see us move ahead with PSR-15 without standardizing on the handler-interface, which is useful not only for delegates, but also for high-level interoperability of the dispatcher/pipe implementations that are necessary to run PSR-15 middleware.

The discussions surrounding the other interfaces will take as long as they take, but this might help us focus the discussion on the first, most central interface, before diving into the (arguably somewhat less important) interfaces.

Thoughts?

@schnittstabil
Copy link
Member

Sure, I'll create a PR.

@simensen
Copy link

@schnittstabil How is this going? I'd love to see where this is going and would like to help however I can. I'm anxious to get PSR-15 moving along again. :)

@schnittstabil
Copy link
Member

Sorry, I'm very busy these days. Currently, if we focus on one single Interface, two of the main problems are:

  1. The name HTTP Message Strategies does not make sense anymore.
  2. Same holds for the Why Bother? section.

To make a long story short, It's a big change, removing the interfaces is the smallest part. And reasons mentioned at the FIG group have to be reviewed/incorporated too. And not to forget: the new version have to be reread multiple times: Is the document structure sensible? Is it as a whole sensible? Etc.

im-sciencing-as-fast-as-i-can

😉 Sorry for the delay, I'm working on it.

@simensen
Copy link

This sounds a lot more complicated than I was thinking it would be. :-/ What sort of realistic expectations could we set for how this might impact PSR-15 if it is being held for the process(req):res interface?

@mindplay-dk
Copy link
Author

mindplay-dk commented May 30, 2017

Just a thought, but rather than mangling your existing documents, perhaps it would be simpler/faster to just start a new PSR from scratch? Although it would be nice to have an exhaustive meta, the PSR itself is extremely simple and likely doesn't need a ton of documentation?

As for the name... "HTTP Request Processing" or "HTTP Request Handling" maybe?

Let me know if/how I can help.

@schnittstabil
Copy link
Member

I'm really sorry for the delay–again. (I've took up a new job this week :bowtie:)

… just start a new PSR from scratch?
As for the name... "HTTP Request Processing" or "HTTP Request Handling" maybe?

Mhm, why not "HTTP Request Handler"? It would align with:

  • PSR-3 Logger (Interface)
  • PSR-11 Container (Interface)
  • PSR-14 Event Manager
  • PSR-15 HTTP Middlewares
  • PSR-17 HTTP Factories

@mindplay-dk
Copy link
Author

Perfect :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants