zendframework / zend-stratigility

Middleware for PHP built on top of PSR-7 and PSR-15
BSD 3-Clause "New" or "Revised" License
235 stars 57 forks source link

Stratigility - `$next` type hint and project naming question #6

Closed sophpie closed 9 years ago

sophpie commented 9 years ago

Hello there,

I have many questions regarding Stratigility Why choosing a name that will confuse everyone knowing Apigility ?

Why getting rid of object implementation and only use Callable ? It will cause unused middlewares to be instantiated and serialization issues.

Finally, how to deal with middleware having more than one children (not just one "next" callable), why having not implemented real workflow around it ?

It is not just about complaining, I am ready to code what necessary. I just wanna know if this architecture have a purpose I cannot see and is already validated.

Ocramius commented 9 years ago

Why choosing a name that will confuse everyone knowing Apigility ?

Personal opinion, but I like the fact that it follows the pattern. The -gility suffix is actually applicable to different projects with different use-case.

It will cause unused middlewares to be instantiated and serialization issues.

You shouldn't serialize stuff that isn't data, tbh.

Finally, how to deal with middleware having more than one children (not just one "next" callable), why having not implemented real workflow around it ?

Each middleware may ship with others as well (kind-of like a chain of responsibility). Please refer to http://stackphp.com/ for an explanation of how this may be done.

sophpie commented 9 years ago

So I can infer architecture is already validated and will not change ?

Ocramius commented 9 years ago

So I can infer architecture is already validated and will not change ?

Can you elaborate on this? I think there is some misunderstanding.

sophpie commented 9 years ago

Just meaning that it seems Stratigility has already been designed and will not propose something different from stackphp (OOP ?). To me, middleware is much more than just dispatch request into sequenced handlers. Using request is to restrictive, middleware is "message" based, having to deal with the whole HTTP layer between middlewares seems too heavy to me. Microservices are not considered (yet ?) in Stratigility but may becomes the mainstream architecture very soon. Data-sharing (cache, session) between middlewares is not (yet ?) supported, because of "functional" oriented programming design. Is there a workaround to deal with that ? Workflows are also much more complexe, because of conditional forking / merging branch process that have to be considered. May be examples could be added to explain how to deal with these process. So, My question could also has been "Is that all about middleware in ZF ? or do we have to expect something bigger or not ?"

Ocramius commented 9 years ago

@sophpie stratigility itself can be used as a middleware, afaik. Each middleware plugged into stratigility may as well be a micro-service. Cache/session are also trivial to implement with what we have here, IMO.

May be examples could be added to explain how to deal with these process.

Yeah, some small examples with simple functions wrapping either around stratigility or used as middlewares plugged into stratigility. Caching/session/auth are good/simple examples to be written: what is the problem here?

So, My question could also has been "Is that all about middleware in ZF ? or do we have to expect something bigger or not ?"

I have to drop the ball here, I'm not understanding what is being asked :-(

weierophinney commented 9 years ago

Why choosing a name that will confuse everyone knowing Apigility ?

It's actually to provide affinity with Apigility, and indicate they are (or will be) related products. In this particular case, Stratigility exists in large part because we validated a middleware-based approach for Apigility using node.js, and plan for the next major version of Apigility to use that approach. It will build on top of Stratigility (or, rather, be Stratigility-based middleware).

Why getting rid of object implementation and only use Callable ?

We're type-hinting on callable to allow any PSR-7-based middleware that's invokable (anonymous function, functor (object with __invoke() method), or PHP callback) to be consumable. It's very typical for middleware libraries to do this, even when an interface is present (e.g. Zend\Stratigility\MiddlewareInterface). We will recommend that developers implement the interface, but we do not require it, in order to allow easy bridging between middleware implementations.

It will cause unused middlewares to be instantiated and serialization issues.

I'm not sure I agree. One benefit to using callables is that you can lazy-load by wrapping in an anonymous function:

$app->pipe('/blog', function ($req, $res, $next) use ($services) {
    $blog = $services->get('My\Blog\Middleware');
    return $blog($req, $res, $next);
});

Alternately, many routing middleware implementations I've seen will handle the lazy-load aspect for you. As an example from many middleware implementations:

$app->get('/images/:id', 'ImagesMiddleware');

would lazy-load the ImagesMiddleware prior to executing it if the route and method are matched.

In terms of serialization issues, can you elaborate on where you see them occurring? You typically don't serialize your middleware…

Finally, how to deal with middleware having more than one children (not just one "next" callable), why having not implemented real workflow around it ?

You nest them just like in Rack, Stack, or WSGI:

// This assumes a routing middleware that provides dynamic routes
$blog = new RoutingMiddleware();
$blog->pipe('/:id.html', $blogPostMiddleware);
$blog->pipe('/', $listPostsMiddleware);

$app->pipe('/blog', $blog);

In the above example, $app composes a $blog, which is itself middleware, which in turn composes additional middleware for providing lists of posts and individual posts, each of which is middleware and could itself be MiddlewareInterface instances or MiddlewarePipe instances. Inside, they might look like this:

// Inside the blog post middleware
if (! $found) {
    // post not found!
    return $next($req, $res); // Calling next === delegating to list posts middleware
}
// Found it, so:
$res->getBody()->write($render($post));
return $res; // returning response bubbles out!

Middleware also lets you compose behaviors. For example, this is what we played with with Apigility:

$app->pipe($router);
$app->pipe($versioning);
$app->pipe($authentication);
$app->pipe($options);
$app->pipe($authorization);
$app->pipe($contentNegotiation);
$app->pipe($validation);
$app->pipe($dispatcher);
$app->pipe($problemDetails); // error handling!

These are all at the outer layer. The dispatcher then dispatches the middleware selected by routing, which itself can be a middleware pipe, delegating to other middleware; in the node.js version, we actually had it delegate to different middleware for each HTTP method (and, in the case of REST resources, different middleware for collections vs entities, and then segregated by HTTP method). This makes for nice, single-purpose classes that do exactly one thing and handle very specific requests. Additionally, having the pipeline means that you end up with easier error handling, and the ability to compose your own features (which means you can also replace any given middleware with your own implementation).

I just wanna know if this architecture have a purpose I cannot see and is already validated.

Hopefully the above answers many of your questions. We modeled Stratigility on Connect, on which Express is based; these have had years of usage, and have proven the pattern.

sophpie commented 9 years ago

Not sure I am agree with all your answers, but they explain the choices you made and it close the issue to me.