-
Notifications
You must be signed in to change notification settings - Fork 2.6k
[ZF3] [RFC] Embracing stateless/immutable state #5599
Comments
In ZF2 the Request and Response objects from the ServiceManager, I didn't change or introduce that. |
@devosc yes, and we should change that |
👍 |
1 similar comment
👍 |
Interesting how about parts that need to remain a state like everything with a session? |
@mech7 they should be created and discarded during |
If think this is potentially the most fruitful RFC for ZF3. |
@ronan-gloo the MVC is actually not that big problem. Areas that are hairy are:
Otherwise it looks already really good for basic usage: by excluding the view layer entirely (using the JsonRenderer, for example) you can get quite impressive results already, but you cannot get any parallelism (if using pthreads) yet. |
I'm just thinking about routing stuffs: Actually, the router, which is a shared service, is created depending on request (console / http), which means, if i understood you well, this part should becomes, in stateless and actual implementation perspective, a non-shared service. While we are, in mostly apps we wrote with Zf2, creating some shared services depending on request (route, verb, and so on), it have some consequences in the userland code. Parallelism is one of the great goal of this approach, by scoping some state-full services in request life-cycle. |
Yes, the router should not be instantiated depending on the request, but should instead fetch the correct instance to dispatch against at runtime (pseudo - ignore CS): class BaseRoute implements RouteInterface {
public function __construct(HttpRoute $httpRoute, ConsoleRoute $consoleRoute) { /* ... */ }
public function match(Request $request) {
if ($request instanceof HttpRequest) { return $this->httpRoute->match($request); }
if ($request instanceof ConsoleRequest) { return $this->consoleRoute->match($request); }
throw new Nope();
}
}
Yes, that is a common mistake, and we can't really fix it in userland's apps. What we can do is providing some base tests that show how broken an app could become.
I am not sure if |
Sort of defeats the purpose of lazy loading? |
Documentation, documentation, documentation. (this is how it's done ... not like this) Poor documentation should not be a monetezation scheme (e.g. paid training, certifications, support). |
Lazy loading is not required in that scenario
Never been like that. Yes, a test case running multiple dispatches and verifying results is worth writing. Also OT. |
We use the shared service indicator so that we can have one service manager for the entire application. However I think in order to ensure scope correctly, each component should have their own service manager; but this becomes problematic from a configuration point of view, hence there is one manager that supports the shared flag. I don't think it would be feasible to achieve that type of perfection - most applications really wouldn't need it (and those components could be their own applications on different machines). And that is only one part of the problem. If the service manager was configured with instances, instead of lazy loading them, then it should be possible to achieve a stateless system as you described; anything not immutable would be passed as parameters. That would be hope anyway. Anything further down the chain would need to have a listener further up to be able to capture that dependency and inject, e.g. the view url help would need a listener for the |
What you are talking about is using a real injector instead of a container. That doesn't make much of a difference in my opinion, but it surely mitigates the risk of having mutable objects all around the place. It is possible to build a stateless system even with the current lazy loading approach, we'd just need to remove the "shared" flag for all these fake services as a first step (and then see what breaks, because it will break). Perfection is not a requirement - you will never reach it anyway. It may be a good use case for revamping |
I've added some of the discussed flaws to the todo list - please add more bullet points if you think you found one. |
I wasn't able to not share the Config, Event Manager, RouteMatch, Response and Service Manager; things still seem to work. The response probably could/should not be shared... still pondering the RouteMatch. |
The RouteMatch is a value and should therefore not be treated as a service |
:) see my previous comment about it ... |
Yes, you wouldn't be able to access the RouteMatch (for example) if it wasn't passed to you as a parameter somehow - that's perfectly acceptable IMO, but it indeed requires some change or to make services that are getting those "values" injected non-shared so that there are no risks. |
I've also been wondering whether factories (or the service config), could also indicate whether a service is shared. The calling code may not always be the right end to control it from. |
@devosc the factory just represents the instantiator for a particular service - it doesn't have any understanding of the lifecycle of the produced object |
Yeah but the same argument could be applied the other way around also. Otherwise why not making everything not shared by default, and specify which ones can be shared ... and if its only because the application knows its reusing the same service manager, then its the application config that could/would manage it, and the factory is what transforms the configuration into a service... I've been wondering if both should be allowed to manage it... Anyhow I understand what you're saying... |
That's because most users will still use ZF2 the "traditional way" |
The service manager uses both a configuration and instance container (e.g. $instances) to retrieve a service. When the service manager is serialized, the container can be reset (emptied). When it is unserialized it should still work as expected as new instances will be created from its configuration and placed into the service container again. This would allow components to have individual service managers with their own configurations (there might be some duplicate configurations across components, which makes it self contained), and those managers can use a shared service container, e.g View Manager could shared the main SM service container and immediately have the RouteMatch available. The service container can also be used as a registry, but unless that component has a configuration for it, it will be lost if the service manager gets serialized. |
Serialization is not relevant here - it is not possible to serialize a container because you don't know if the objects it keeps can be serialized. |
This issue has been closed as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html |
ZF's slowness and bootstrapping
The problem of ZF2's performance is mainly related with having a huge "bootstrap" step (loading modules, configuring the service manager, instantiating core services).
Today, I got back at looking at
OcraHopHop
after looking into @rdlowrey's amazing job in buildingAerys
(not yet released), which is basically the kickass nonblocking stuff of node.js minus the horror of javascript (that is my take on it, feel free to insult me :) ).Avoiding bootstrap
The idea is simple: run PHP without a web server as an own http service. Something like:
./run-my-application.php --port=80 &
This could work with any PHP application, as long as we embrace immutable state.
OcraHopHop
worked fine with this concept, and I obtained impressive results with an 80% or faster ZF2 application, but there are some limitations related to state, which prevents asynchronous operations and eventually threading.What "stateless" means
For those who don't have a clear mind on what I mean, this is what a stateless application should look like:
This is just pseudo-code, and of course we cannot serialize an application since it contains un-serializable objects, but you get the point.
You can read up more about what I mean with "stateless" on @igorw's blog
What the problem is
Try it for yourself! Try running
OcraHopHop
against the normal "Hello World" skeleton application example - it will slow down after a dozen requests, and the output will change at every request (more and more broken at every iteration).That is because ZF2 internals are not immutable. We have to get rid of that.
TODOs
This issue is mainly here to:
Identified components with problems so far
I don't yet have a list of components that retain state, but so far I can tell that a lot of plugins in the view layer keep an internal state, as well as the
Request
object (which is a service, see this page again on why we should NOT do that!), theResponse
object, theMvcEvent
and other stuff that should not be available via getters.The text was updated successfully, but these errors were encountered: