-
Notifications
You must be signed in to change notification settings - Fork 653
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
Refactoring API client Paginators for better refactored API Client #439
Comments
Thanks for this work! This will really improve the testability of code that uses this SDK. I have a few questions / comments. Proposed API Client Paginator Refactor
Why exactly can't this use the granular Using the refactored paginatorI know we discussed offline a few different ways to do pagination. I acknowledge that the variety of AWS service APIs/behaviors constrains how much the SDK can do in the area of pagination (which makes a cleaner Kubernetes-like API difficult or impossible). I have one more suggestion on the pagination API. Consider this example from the proposal:
I think there is a design smell here: Why can To do this, I think the cleanest way is the following: p := s3.NewListObjectsPaginator(input, client)
for p.HasNextPage() {
tp, err := p.NextPage(ctx)
if err != nil {
return fmt.Errorf("failed to list objects, %v", err)
}
}
Do you agree that there is a code smell / that this resolves the code smell? Is my proposal possible given the wide variety of API behaviors that the sdk supports? Mocking the paginator
Is it / will it be possible for there to be paginator-specific options? I am concerned with the mixing of production code in unit tests (the return value from |
Thanks for the feedback and suggestion #530 PR provides a prototype based on these designs, and clarifies some of the behavior. We took your suggestion for paginators and simplified their methods. Instead of having a a Next method that returns a bool, a get current page, and an error method, There is just the two methods, // Use the paginator to iterate over pages returned by the API. HasMorePages
// will return false when there are no more pages, or NextPage failed.
for p.HasMorePages() {
o, err := p.NextPage(context.TODO())
if err != nil {
log.Fatalf("failed to get next page, %v", err)
}
fmt.Println("Page:", o)
} |
Example of a hand written paginator that the SDK code generation should use // ListObjectsV2APIClient provides interface for the Amazon S3 API client
// ListObjectsV2 operation call.
type ListObjectsV2APIClient interface {
ListObjectsV2(context.Context, *ListObjectsV2Input, ...func(*Options)) (*ListObjectsV2Output, error)
}
// ListObjectsV2PaginatorOptions provides the options for configuring the
// ListObjectsV2Paginator.
type ListObjectsV2PaginatorOptions struct {
// The maximum number of keys to return per page.
Limit int32
}
// ListObjectsV2Paginator provides the paginator to paginate S3 ListObjectsV2
// response pages.
type ListObjectsV2Paginator struct {
options ListObjectsV2PaginatorOptions
client ListObjectsV2APIClient
params ListObjectsV2Input
nextToken *string
firstPage bool
}
// NewListObjectsV2Paginator initializes a new S3 ListObjectsV2 Paginator for
// paginating the ListObjectsV2 respones.
func NewListObjectsV2Paginator(client ListObjectsV2APIClient, params *ListObjectsV2Input, optFns ...func(*ListObjectsV2PaginatorOptions)) istObjectsV2Paginator {
var options ListObjectsV2PaginatorOptions
for _, fn := range optFns {
fn(&options)
}
p := &S3ListObjectsV2Paginator{
options: options,
client: client,
firstPage: true,
}
if params != nil {
p.params = *params
}
return p
}
// HasMorePages returns true if there are more pages or if the first page has
// not been retrieved yet.
func (p *ListObjectsV2Paginator) HasMorePages() bool {
return p.firstPage || (p.nextToken != nil && len(*p.nextToken) != 0)
}
// NextPage attempts to retrieve the next page, or returns error if unable to.
func (p *ListObjectsV2Paginator) NextPage(ctx context.Context) (
*ListObjectsV2Output, error,
) {
if !p.HasMorePages() {
return nil, fmt.Errorf("no more pages available")
}
params := p.params
if v := p.options.Limit; v != 0 {
params.MaxKeys = &v
}
result, err := p.client.ListObjectsV2(ctx, ¶ms)
if err != nil {
return nil, err
}
p.firstPage = false
if result.IsTruncated != nil && *result.IsTruncated == false {
p.nextToken = nil
} else {
p.nextToken = result.NextContinuationToken
}
p.params.ContinuationToken = p.nextToken
return result, nil
} |
|
This issue details how the v2 AWS SDK for Go's API client Paginators can be updated based on the API client refactor proposed in #438.
Since, the API client operation methods no longer return a built Request, (e.g.
GetObjectRequest
), the paginator needs be refactored.Proposed API Client Paginator Refactor
The proposed refactor of the SDK's clients is split into four parts, API Client, Paginators, Waiters, and Request Presigner helpers. This proposal covers the API client Paginator changes.
The following is an example of the existing S3 ListObjects Paginator.
The paginator's constructor would be updated to take an interface for the paginated operation client, in addition to the operation's input parameters. The constructor would continue to return the operation paginator type, (e.g.
ListObjectsPaginator
). The paginator'sNextPage
) method would be updated to take functional options for modifying the request per pages, in addition to aContext
value.The following is an example of the proposed construction of a S3 ListObjects Paginator.
The following is an example of the updated ListObjectsPaginator's constructor and method signatures. The client parameter is a unnamed interface to prevent additional types defined for each operation paginator and circular package imports with the
api
package where theListObjectClient
interface is defined.Using the refactored paginator
The application creates a paginator for an operation by calling the paginator's constructor, passing in the client and input parameters. Once the application has created a paginator, (e.g.
ListObjectsPaginator
), the application can iterate over the response pages using the operation paginator'sNextPage
,CurrentPage
, andErr
methods.The
NextPage
method must be called to iterate through the operation response pages. The method takes acontext.Context
and optionalaws.Request
functional option. The request functional options provide you the ability to customize the operation call for each page, such as adding headers, or tracing.The following example shows the
ListObjectsPaginator
being created, and pages iterated over.CurrentPage
returns theListObjectsResponse
for each page. If an error occurs,NextPage
will return false and the for loop will break out. The application code using the paginator should check for an error via the paginator'sErr
method.Mocking the paginator
To mock out an operation paginator, the application's test should provide a mocked API client implementation for the operation being paginated. The operation specific API client should return the mocked operation responses for each page, or error behavior of the application logic being tested.
This experience could further be improved if the SDK were to generate helper mocked API Clients for the paginated operations. The mocks could take a list operation responses for the mock client provide to the paginator.
The following example of application code that takes a
ListObjectsClient
interface. Using the client interface allows the application's test code to provide mocked behavior for paginatingListObject
operation.The following example defines a mock type that implements the API client's
ListObjects
operation method. The application will pass this mocked API client to the paginator's constructor.Using the mock ListObjectsClient, the application's unit test can replace the behavior of the API client so that the ListObjectsPaginator used by
listEmptyObjects
behavior can be mocked.The text was updated successfully, but these errors were encountered: