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

Add PageStreamer to make it simple to fetch paged lists #654

Merged
merged 1 commit into from Jan 19, 2016

Conversation

jskeet
Copy link
Collaborator

@jskeet jskeet commented Jan 12, 2016

Fixes issue #632, as far as we want to for now.

This will only make the PageStreamer available - it won't be used in generated code.
We will want to add examples of how to use this.

Note that the only async option for the moment is to retrieve a whole list asynchronously.
While it would be nice to do this more lazily, that would need something like Ix-Async, and we'd prefer not to
introduce a new dependency for this at the moment.

@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Jan 12, 2016
/// to provide another request. (The provider may build a new object, or modify the existing request.)</param>
/// <param name="responseFetcher">A function to fetch a response containing a page of results given a request.</param>
/// <returns>A sequence of resources, which are fetched a page at a time. Must not be null.</returns>
public IEnumerable<TResource> Fetch(TRequest initialRequest, Func<TRequest, TResponse> responseFetcher)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@gabikliot
Copy link

Also, for semantics of async fetch: in addition to the 2 options you provided, don't you think we also want a 3rd option: asynchronously fetch them one by one. Kind of like
Task<TResource> FetchNext and the calling code has to check the token to decide if there is indeed next. Something like Azure Table CloudTable.ExecuteQuerySegmentedAsync method.

This option is important when you can't store all resources in memory at once, so you have to load them one by one, in async way.
Perhaps that is what you meant by Ix-Async? I think you can do something like ExecuteQuerySegmentedAsync without introducing any new dependency.

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 12, 2016

That 3rd option is what we would have if we added the dependency to Ix-Async, so we could return an IAsyncEnumerable<T>. But I don't want to either a) make up my own equivalent which would be subtly different and incompatible with Ix-Async or b) take a dependency on it. I don't think the benefit is worth the downside at this point.

We can potentially introduce a separate project which depends on both Ix-Async and this project, and adds extension methods to PageStreamer (which would need to expose a bit more of its functionality, admittedly).

@gabikliot
Copy link

I definitely would NOT take dependence on IAsyncEnumerable<T>, not until async RX is an official part of .NET libraries (which may happen at some point, I know some people who are working on that).
But why not to mimic what Azure Storage is doing, like ExecuteQuerySegmentedAsync? You already introduced a Token in the PageStreamer API.

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 12, 2016

Which of the 16 overloads of ExecuteQuerySegmentedAsync are you referring to? The point of PageStreamer is to avoid the caller having to deal with the continuation token themselves at all. If they're going to have to manage it, PageStreamer won't provide any actual benefit - they can just call the existing API in a loop. Perhaps if you could provide a sample of what you'd expect the client code to look like - assuming an existing PageStreamer it would clarify things.

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 12, 2016

Btw, one downside of this PR compared with the original code is that this was originally written with the expectation of it being called by generated code, mostly. But here's an example of showing the same sort of thing in manual code:

https://github.com/GoogleCloudPlatform/gcloud-dotnet/blob/master/GoogleApiWrappers/src/Google.Apis.Storage.v1.ClientWrapper/StorageClient.ListBuckets.cs#L77

(It's possible that the lambda expression could be replaced by a method group conversion. I haven't checked.)

@gabikliot
Copy link

Ohh, I see you point now about "The point of PageStreamer is to avoid the caller having to deal with the continuation token themselves at all".
I definitely did mean the option when the caller deals with tokens himself. That is why I shared the stackoverflow usage example: the caller is calling querySegment.ContinuationToken != null.

I agree that "they can just call the existing API in a loop" and that PageStreamer is an abstraction above that.
We (all Google Apis) just need to make sure we do expose the Token from all the segmented APis, so the caller can check querySegment.ContinuationToken != null.

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 12, 2016

Right - I believe we're already good with that. In particular, if the token isn't exposed, we wouldn't be able to construct the PageStreamer to start with :) Sounds like we're on the same page now, no pun intended. Would love Ix-Async to be available already - with C# language support, ideally...

(The gRPC C# tooling already has a dependency on it, btw.)

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 12, 2016

Just had another thought about this - as in this location PageStreamer is all about the Google API Client library (rather than, say, gRPC) we can add constraints on the request type, and avoid one parameter. Will have a look at that, and add it as another commit in the same PR.

@gabikliot
Copy link

Sounds like we're on the same page now

Yep. 👍

As for Async-Rx: yes, Bart De Smet was promising it to us a long time ago. Can't wait for that to happen.

There is also a new kid on the block: coreclr team it prototyping with a new Async Channels and AsyncEnumerator

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 12, 2016

Humbug - can't do that, as IClientServiceRequest is in GoogleApis, not GoogleApis.Core.

@peleyal If I were to move PageStreamer into GoogleApis, would that feel odd? What namespace would you expect it to be in? It would definitely make it easier to call PageStreamer.

@@ -0,0 +1,137 @@
// Copyright 2015 Google Inc. All Rights Reserved.

This comment was marked as spam.

This comment was marked as spam.

@peleyal
Copy link
Collaborator

peleyal commented Jan 12, 2016

I only reviewed the PageStreamer (not tests yet).
Sorry for joining the discussion a little bit late, I hope that my comments make sense.

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 12, 2016

Due to the scale of the changes, I've now squashed and repushed. The good news is that it's now significantly easier to call, and slightly easier to construct.

For example, the GCS client code listing buckets would be:

private static readonly PageStreamer<Bucket, BucketsResource.ListRequest, Buckets, string> bucketPageStreamer =
    new PageStreamer<Bucket, BucketsResource.ListRequest, Buckets, string>(
       (request, token) => { request.PageToken = token; return request; },
        buckets => buckets.NextPageToken,
        buckets => buckets.Items);
...

// Create the initial request as before, then use the page streamer synchronously
// or asynchronously.
var request = ...;
foreach (var bucket in bucketPageStreamer.Fetch(request))
{
    ...
}

// Or
var buckets = await bucketPageStreamer.FetchAllAsync(request, cancellationToken);

@mmdriley
Copy link
Contributor

lgtm

using Google.Apis.Requests;
using Google.Apis.Discovery;
using Google.Apis.Services;
using System.IO;

This comment was marked as spam.

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 13, 2016

Fixed using directive order, updated copyright year and added a TODO for a doc comment example.


internal Request WithToken(string token)
{
return new Request(resource) { Token = token, Check = Check };

This comment was marked as spam.

This comment was marked as spam.

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 15, 2016

I've rebased this to Matt's reorganization, and squashed it down to a single commit. I believe it's all sensible...

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 18, 2016

@mmdriley @peleyal Please review the latest commit carefully (before I squash...)

While rewriting the tests slightly, it struck me that within the context of google-api-dotnet-client, it's reasonable to assume that all requests objects are mutable, and so simplify the "provide a request object with this page token" to "just modify the existing request". This is basically what people would be doing anyway, and now it's unambiguous. The setup code is now simplified to:

private static readonly PageStreamer<Bucket, BucketsResource.ListRequest, Buckets, string> bucketPageStreamer =
    new PageStreamer<Bucket, BucketsResource.ListRequest, Buckets, string>(
        (request, token) => request.PageToken = token,
        buckets => buckets.NextPageToken,
        buckets => buckets.Items);

{
private static readonly PageStreamedResource simpleResource = new PageStreamedResource(
"simple",
new Page(null, 1, 2, 3),

This comment was marked as spam.

@peleyal
Copy link
Collaborator

peleyal commented Jan 18, 2016

LGTM, thanks Jon!
I just left one comment regarding TPL, which I'm not actually sure about, but it's worth to mention it here

@mmdriley
Copy link
Contributor

LGTM. Sorry again for pulling the code out from under you mid-PR.

@jskeet
Copy link
Collaborator Author

jskeet commented Jan 19, 2016

@mmdriley: No problem at all. Given that this was just new code, it was probably the simplest possible change to make under that scenario :)

Will squash and merge.

Fixes issue googleapis#632, as far as we want to for now.

This will only make the PageStreamer available - it won't be used in generated code.
We will want to add examples of how to use this.

Note that the only async option for the moment is to retrieve a whole list asynchronously.
While it would be nice to do this more lazily, that would need something like Ix-Async, and we'd prefer not to
introduce a new dependency for this at the moment.
jskeet added a commit that referenced this pull request Jan 19, 2016
Add PageStreamer to make it simple to fetch paged lists
@jskeet jskeet merged commit 43b90b0 into googleapis:master Jan 19, 2016
@jskeet jskeet deleted the page-streamer branch January 19, 2016 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants