Closed joelwurtz closed 9 years ago
Nice proposal @joelwurtz. Not sure I understand every point of it at this time (as I am supposed to be in bed for a few hours now), but seems to be a clean description of the problem and advantages/disadvantages of the solution. Will take a closer look tommorrow.
thanks for bringing this up. i am +1 on your proposal. indeed the current sendRequests method could send requests async but would need to wait for all of them to resolve before returning, or it would violate the contract of always throwing BatchException if just one request failed.
i also agree with the roadmap. lets drop the batch things for now and release alpha 1. if we are ready with async for 1.0, that would be very good. otherwise it can come later as its just addition - and a separate interface anyways.
@dbu keep in mind that the proposal includes dropping the sendRequests method which would be a BC break at a later time. Although we are about to release an alpha, we should definitely drop the sendRequests method prior to the stable, even if we plan to add async requests later.
i think the sendRequests method actually is a weird idea when we have the async method. so it should be removed from the interface and not added back. when doing async, things should look different than this method does, and forcing non-async clients to accept a bunch of requests is just complicating things. consumers can loop over their requests themselves, no big deal.
if there is need for sending a bunch of requests, we can provide a utils class similar to HttpMethodsClient that can wrap either a HttpCient or a AsyncHttpClient and offers sendRequests that will be either async or sequential, depending on the client used with it.
but this PR can't be merged as is, as it already adds the async interfaces. i would opt to only remove for now and tag alpha 1 today.
Okay, preparing PR to remove sendRequests
.
Should I remove BatchResult and BatchException as well? (Since there is no need for them)
yes we should remove them. and if we do a BatchClient in the utils, we can add them there in a async-friendly fashion.
See #75
@joelwurtz do you want to create a new PR and label it as WIP for the async interface? the rest of this PR (the deletion) has been merged.
Yeah, this PR would probably be hard to rebase. But you can overwrite your commits, so that the PR and the branch can be preserved.
whatever is easisest. i copy the description into #75
@joelwurtz do you want to create a new PR and label it as WIP for the async interface? the rest of this PR (the deletion) has been merged.
will do that
By working on a PoC of the Plugin system and some of the adapters, the sendRequests (multiple aproach) add a great complexity to all of this:
Actual problems
On the plugin system
First we start on thinking on a middleware chain (like stackphp.com) , but having the sendRequests method will imply a duplication on the execution (for each plugin we have to iterate over the requests, and the response, and futhermore we need to be extremly careful for the indexation of the reponse because of immutabilty see https://github.com/php-http/documentation/issues/11)
We then introduce the concept of having a pipeline chaining for this plugin, which is the concept i begin to implement in https://github.com/joelwurtz/plugin-client.
But with this approach, when trying to add the status code in error to exception plugin, i needed to update the transformResponse process, by adding the request to it which feels not right and not needed for all the plugins except this one (see https://github.com/joelwurtz/plugin-client/commit/d9d1b0d0ea2e189de90084788d749671281c5cc8#diff-3c253bd0a84a7e12f416110d65e05351R15)
Also the Retry plugin would not be possible as we need to encapsulate the sendRequest with a try / catch which is impossible with a pipeline, so we are back to the middleware (see the discussion on slack with @sagikazarmark), but would imply to handle the complexity for the sendRequests method on each plugin :/
On the adapter
Implementing the sendRequests method, on Guzzle6 adapter by exemple, indue to have all the requests / response in memory, which can be heavy on the system when there is many requests, or requests with large stream (even if this can somehow "handled").
Also as we have to stick with the BatchResult interface, we have for the moment, in all the adapter a dependence on the utils package for the implementation, as there is no storage system in Guzzle, neither in React as they all used a Promise system for the multiple request system.
Proposed solution
Move sendRequests method to an Implementation
sendRequests, is IMO a simple method, which simply take an array of requests and return an array of response. This can be easily done in an implementation which take an HttpClient and pipeline this array of requests and return a BatchResult, doing this will make HttpClient much easier and also the middleware solution would not suffer of implementing this method and all the complexity that comes with, as the batch system would be on an upper level.
Parallelism
One of the disavantage of doing this, is that we will drop support for library that can sends multiple request in parallel.
However sending requets in parallel, is just sending multiple request in an asychronous way.
Async Client
To support this, i propose a new interface: HttpAsyncClient which only send a RequestInterface object and return a Promise instead of the ReponseInterface, to support async client (guzzle5, guzzle6 and react)
The multiple requests client can then also accept a HttpAsyncClient and use it for sending request instead of the HttpClient.
Another advantage, is that it will allow to send one request in an asynchronous way, for the moment the only way to do that is to have the knowledge that the implementation send multiple requests in an asychronous way and use the sendRequests method with only one request:
For the plugin system, it would be also easier, IMO, to handle a Promise rather than a batch result Also, it would be possible to provide an abstract implementation to handle the middleware chaining for both type of client (HttpClient and HttpAsyncClient), so plugin can only focus on the transformation of the response / request system rather than the logic of the sendRequests method.
For adapter sending parallels / async requests by using a Promise system (react / guzzle), it would be easy to wrap their Promise with our own implementation, this will imply no storage on our end, and then no memory problem.
Final word
I don't think we should add this Promise / Async system in the upcoming alpha, as it's too early.
However IMO we should drop the sendRequests from the interface and add it in an implementation under the utils library.
Then in a another release we can easily add the parralelism / async in a new interface without breaking stuff (so we can release this in a 1.1 / 1.2 ... not in a 2.0).
This would also make things easier and focus more on the plugin system / adapter implementation which is the real value of php-http.
Icing on the cake
With the HttpAsyncClient we could also provide a new virtual package :
php-http/async-client-implementation
.So if a library is doing some heavy works on http and want to force their users to use an async client it would be possible, also user will have a real view on which implementation can do asynchronous request or not.